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.