Reputation: 2472
This code is a simple view. I have a singleton called UserProfile
which is called early on in the app lifecycle. When a user registers, logs in, or is logged in, the details for that User profile are set. Then this view is eventually presented to the user. The code in question is @ObservedObject var profile = UserProfile.sharedProfile
. if I remove that line of code, the Text(...)
views won't display anything at all. If I have that line of code, they work just fine. The singleton is being initialized at app launch when it reaches my base view.
How do I do this a better way so that I can always just access the UserProfile.sharedProfile
?
struct BaseHomeScreenView: View {
@ObservedObject var baseHomeScreenViewModel = BaseHomeScreenViewModel()
//This object is not being used anywhere but it does affect the view below.
@ObservedObject var profile = UserProfile.sharedProfile
var body: some View {
ZStack {
VStack {
Text("Hello")
Text(UserProfile.sharedProfile.firstName)
Text(UserProfile.sharedProfile.lastName)
Text(UserProfile.sharedProfile.email)
Image(uiImage: UserProfile.sharedProfile.profileImage)
}
}
}
}
class BaseViewModel: ObservableObject {
@Published var userFlow: UserFlow = .login
init(){
if Auth.auth().currentUser != nil {
userFlow = .home
UserProfile.sharedProfile.fetchProfileInfo()
} else {
userFlow = .login
}
}
enum UserFlow {
case onboarding, login, home
}
}
class UserProfile: ObservableObject {
static var sharedProfile = UserProfile()
@Published var profileImage: UIImage
@Published var firstName: String
@Published var lastName: String
@Published var email: String
init() {
self.profileImage = UIImage()
self.firstName = ""
self.lastName = ""
self.email = ""
}
func setUserData(profileImageURL: String, firstName: String, lastName: String, email: String) {
fetchImageFromURL(url: URL(string: profileImageURL)!)
self.firstName = firstName
self.lastName = lastName
self.email = email
}
func fetchProfileInfo() {
guard let uid = Auth.auth().currentUser?.uid else {
print("Error: UserProfile.fetchProfileInfo() - Failed to fetch user id.")
return
}
let db = Firestore.firestore()
let fsUserProfile = db.collection("users").document(uid)
fsUserProfile.getDocument { (snapshot, err) in
if err != nil { return }
self.fetchImageFromURL(url: URL(string: snapshot?.get("profile_image_url") as? String ?? "")!)
self.firstName = snapshot?.get("first_name") as? String ?? ""
self.lastName = snapshot?.get("last_name") as? String ?? ""
self.email = snapshot?.get("email") as? String ?? ""
}
}
func fetchImageFromURL(url: URL) {
getData(from: url) { data, response, error in
guard let data = data, error == nil else {
if let error = error {
print("Error: UserProfile.fetchImageFromURL() - Failed to fetch image from URL. | \(error.localizedDescription)")
} else {
print("Error: UserProfile.fetchImageFromURL() - Failed to get data while fetching image.")
}
return
}
DispatchQueue.main.async() {
self.profileImage = UIImage(data: data)!
}
}
}
private func getData(from url: URL, completion: @escaping (Data?, URLResponse?, Error?) -> ()) {
URLSession.shared.dataTask(with: url, completionHandler: completion).resume()
}
}
Upvotes: 1
Views: 197
Reputation: 49590
SwiftUI needs to know when something has changed in order to re-compute the body
of the view that depends on the change.
With objects, it uses an @ObservedObject
property wrapper (or @StateObject
, or @EnvironmentObject
), and the object has to conform to ObservableObject
. When that object signals a change - either by updating its @Published
property or by calling objectWillChange.send()
directly - the SwiftUI knows to update the view.
So, in your example, when @ObservedObject var profile
property "changes", body
is recomputed, and by virtue of recomputing, reads UserProfile.sharedProfile
and its properties.
Upvotes: 2