6

Let's say that I have a class Employee whose picture property can be observed.

internal class Employee: CustomDebugStringConvertible, Identifiable, ObservableObject {
    internal let name: String
    internal let role: Role
    @Published internal var picture: UIImage?
}

With a class that stores an array of employees. This array might be mutated later so it's a @Published property:

internal class EmployeeStorage: ObservableObject {
    @Published internal private(set) var employees: [Employee]
}

Now I have a list of views that uses the employee storage:

struct EmployeeList: View {
    @ObservedObject var employeeStorage = EmployeeStorage.sharedInstance

    var body: some View {
        // Uses employeeStorage.employee property to draw itself
    }
}

And the list is used in a content view:

struct ContentView: View {
    @ObservedObject var employeeStorage = EmployeeStorage.sharedInstance

    var body: some View {
        // Uses an EmployeeList value
    }
}

Let's say that now the EmployeeStorage object changes mutates employee array: this effectively updates the UI and I can see that the list is updated with the new collection of employees. Problem: what if I want to achieve the same effect when an employee's picture property changes? I thought that this was enough, but in my case (I have a more complex example), the UI is not updated in case that an employee's image changes. How to do it?

1
  • Your ContentView has a comment that mentions EmployeeList but you haven’t defined an EmployeeList type. What is an EmployeeList? Is ContentView supposed to display all the employees in an EmployeeStorage, or just a single Employee, or something else? If it’s only supposed to display a single Employee, why does it need the entire EmployeeStorage? Commented Oct 11, 2019 at 16:31

1 Answer 1

4

There are a couple of problems in your code.

  • Employee Model

    • Your model should be a struct.
    • It does not need to conform to ObservableObject.
    • Why you're using UIImage!? Simply use Image and possibly a URL if you're fetching data from a web Api asynchronously.
    • something like:

    // Employee Model
    struct Employee: Codable, Identifiable {
        let name: String
        let role: Role
        let imageUrl: String
        let picture: Image
    }
    
  • EmployeeStorage

    • I suspect that the way you're using EmployeeStorage as a singleton is problematic. Apple suggests that you better use an @EnvironmentObject property to pass data shared between views and their subviews. It's super simple, yet robust!

That being said, you can modify your code as follows:

// Employees Store
final class EmployeeStorage: ObservableObject {
    @Published var employees: [Employee]
}

// Content View
struct ContentView: View {
    var employeeStorage = EmployeeStorage()

    var body: some View {
        ...
        EmployeeList()
        .environmentObject(employeeStorage) // here you pass the storage to the list
    }
}

// Employee List
struct EmployeeList: View {
    @EnvironmentObject var employeeStorage: EmployeeStorage

    var body: some View {
        NavigationView {
            List {
                ForEach(employeeStorage.employees) { employee in
                        NavigationLink(
                            destination: EmployeeDetail()
                                .environmentObject(employeeStorage) 
                        ) {
                            EmployeeRow(employee: employee)
                        }
                }.navigationBarTitle(Text("Employees"))
            }
        }
    }
}

For more information, you can check this out. It's been done pretty much what you're going to achieve.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.