ios – I need help structuring my data in my SwiftUI application. I have a mutating func in my struct but the “if let” mutates the copy and not original


I’m building a SwiftUI application and one of my Models has a mutating func that’s purpose is to help update the UI when changes occur. For example, if a user updates their username I want to just call the mutating func to update the Model and in return the UI.

But, since I am getting this data from the server it has a chance to be nil. So by unwrapping with “if let” it creates a copy and that the mutating func no longer updates the UI. Here is my setup for more context.

ProfileViewModel
This is my Profile View Model. I have an enum of states that helps me on the UI side render based on the state.

@Observable class ProfileViewModel {
    
    enum State {
        case idle
        case loading
        case failed
        case success
    }
    
    private(set) var state: State = .idle
    private(set) var userProfile: UserProfile? = nil
    
    private let manager: UserManager
        
    init(manager: UserManager) {
        self.manager = manager
    }
    
    func getUserProfile() async {
        guard case .idle = state else {
            return
        }
                
        state = .loading
        
        do {
            let profile = try await manager.getUserProfile()
            state = .success
            userProfile = profile
        } catch {
            state = .failed
        }
    }
    
    func signOut() async -> () {
        do {
            try await manager.signOut()
        } catch {
            print(error)
        }
    }
    
}

ProfileView
This is where my issue really starts. Firstly, if we have a .success case that means we got data back. But I still have to unwrap it and that creates a copy. When I call userProfile.updateUsername() it updates the username of the copy and does not cause a rerender on the UI.

What can I do to solve this or better handle my data? I would like to pass the Model to a child view eventually also.

struct ProfileView: View {
    
    @State private var profileViewModel = ProfileViewModel(
        manager: UserManager()
    )
    
    var body: some View {
        VStack {
            switch profileViewModel.state {
                
            case .success:
                if var userProfile = profileViewModel.userProfile {
                    Text("@\(userProfile.username)")
                        .fontDesign(.monospaced)
                    
                    Button {
                        userProfile.updateUsername()
                    } label: {
                        Text("update")
                    }
                }
                
            case .loading:
                ProgressView()
                
            case .failed:
                Text("Sorry, something went wrong.")
                
            default:
                EmptyView()
            }
        }
        .task {
            await profileViewModel.getUserProfile()
        }
    }
}

UserProfile

struct UserProfile: Decodable {
    private(set) var id: String
    private(set) var username: String
    private(set) var fullName: String?
    private(set) var avatarURL: String?
    private(set) var about: String?
    private(set) var website: String?
    private(set) var joined: String
    
    enum CodingKeys: String, CodingKey {
        case id
        case username
        case fullName = "full_name"
        case avatarURL = "avatar_url"
        case about
        case website
        case joined
    }
    
    mutating func updateUsername() {
        username = "justUpdateAndRerenderTheUIPlease"
    }
}

I’ve tried using “if let” and also just checking if the “profileViewModel != nil” but that causes more issues with the optional data.

Latest articles

spot_imgspot_img

Related articles

Leave a reply

Please enter your comment!
Please enter your name here

spot_imgspot_img