Search code examples
swiftuinestedobservablepublish-subscribe

How to dynamically defined an object inside observable objects in swiftui.?


I am new to swiftui and learning it.

I have a situation where I have to define an array of an object(let say the object is USER, so an array of Object will be "[USER]") inside the observable object. This observable object is a ViewModel. There is an API call in any other view which initialize this ViewModel.

I have to initialize this [User] object only after this ViewModel will be initialized. I have to do take this [USER] inside the ViewModel because this object [USER] will be modified by TextField on other views.

I am unable to initialize this [USER] object after the API call response because of several compile-time errors.

struct User: Identifiable {

   var profileName: String = ""
   var profileId: String = ""   
   var firstname: String = ""
   var lastname: String = ""
}


class ViewModel: ObservableObject {

    @Published var document: Document?

    @Published var users = [User]()
    var idValue: String

    init (idValue: String) {
        idValue = idValue
    }
    
    func getDetails() {
        
        APIService.shared.getDocumentDetails(idValue: idValue) { document in
            DispatchQueue.main.async {
                self.document = document
                
                self.setEndUserValues()
            }
        }
    }
    
    func setEndUserValues() {
        
        var users = [User]()
        
        if let document = document, let subjects = document.subjects {
            ForEach(0..<subjects.count, id: \.self) { index in
                let profile = subjects[index]
                var user = User(id: index)
                
                user.profileName = profile.profileName ?? ""
                user.profileId = profile.profileId ?? ""
                user.firstname = profile.firstname ?? ""
                user.lastname = profile.lastname ?? ""
                users.append(user)

            }

        }
    }

I am getting Type '()' cannot conform to 'View' error. Also, the response is quite nested, so I have not mentioned more properties. Please help me to achieve this.


Solution

  • You may find that the following code works a bit better than your fixed code:

    struct User: Identifiable {
        
        var profileName: String
        var id: String // In order to conform to Identifiable, you have to have
                       // a variable named "id"
        var firstName: String // We write Swift variables in camelcase. 
        var lastName: String  // You can even see that these get flagged as a spelling error. 
    }
    
    class ViewModel: ObservableObject {
        
        @Published var document: Document?
        
        @Published var users = [User]()
        var idValue: String
        
        init(idValue: String) { // I am not sure of the purpose of initializing the
                                // ViewModel with an "id". You shouldn't have multiple
                                // copies of this in your app.
            self.idValue = idValue
        }
        
        func getDetails() {
            APIService.shared.getDocumentDetails(idValue: idValue) { document in
                DispatchQueue.main.async {
                    self.document = document
                    
                    self.setEndUserValues()
                }
            }
        }
        
        func setEndUserValues() {
            if let document = document, let subjects = document.subjects {
                for subject in subjects { // You have an array of Subjects. Use it directly.
    
                    // This way of initializing creates your User and appends it to the
                    // array of Users in one step.
                    users.append(User(
                        profileName: subject.profileName,
                        id: subject.profileId,
                        firstName: subject.firstname,
                        lastName: subject.lastname))
                }
            }
        }
    }
    

    I want to show some issues in your code for a minute that I fixed above:

    func setEndUserValues() {
        
        var users = [User]() // You are creating a local array here. This is not your
                             // "@Published var users"
        
        if let document = document, let subjects = document.subjects {
            for index in 0..<subjects.count { // This becomes unnecessary when you don't
                                              // need the index.
                let profile = subjects[index] // This becomes unnecessary.
                var user = User(id: index) // According to your User struct, this doesn't
                                           // compile. You don't have an initializer that
                                           // accepts an "id: Int"
                
                user.profileName = profile.profileName ?? ""
                user.profileId = profile.profileId ?? ""
                user.firstname = profile.firstname ?? ""
                user.lastname = profile.lastname ?? ""
                users.append(user) // This is your local array of users. This doesn't
                                   // actually go anywhere, and you don't actually
                                   // fill your ViewModel.users array
            }
        }
    }