简体   繁体   中英

SwiftUI how to update a view when the object in a published array is updated and not the array itself

The following is a contrived example of the problem I am facing.

Situation: I have a class PersonStore that stores class Person in an array and is Published. The class Person creates a LegalAge model for each Person and the model is Published
Problem: When an update to the persons age in LegalAge is performed the view for PersonStore is not updated.

I'm guessing that the reason there is no change is because the change occurs to the object within the array and not to the array itself. How do I overcome this?

例子


import SwiftUI

enum LegalAge: Int, CaseIterable {
    case UK = 18
    case USA = 21
    
    var name: String { "\(self)" }
}

struct ContentView: View {
    var body: some View {
        MainView()
        
    }
}

/// Displays a list of people and their ages
struct MainView: View {
    @ObservedObject var personStore = PersonStore()
    
    @State private var addPerson = false
    
    var body: some View {
        NavigationView {
            List {
                ForEach(personStore.store) { person in
                    NavigationLink(destination: ShowAge(person: person)) {
                        HStack {
                            Text("\(person.name)")
                            Text("\(person.model.personsAge)")
                            Text("\(person.model.country)")
                            Image(systemName: (person.model.personsAge >= person.model.drinkingAge) ? "checkmark.shield.fill" : "xmark.shield.fill").foregroundColor((person.model.personsAge >= person.model.drinkingAge) ? Color.green : Color.red)
                        }
                    }
                }
            }
            .navigationBarTitle(Text("Can I Drink?"))
            .navigationBarItems(leading: Button(action: { addPerson = true }) { Image(systemName: "plus") }
            )
        }
        /// Open a sheet to create a new person.
        .sheet(isPresented: $addPerson) { AddPerson().environmentObject(personStore) }
    }
}

/// A Person store to hold all the people.
class PersonStore: ObservableObject {
    
    @Published var store = [Person]()
    
    
    func addPerson(person: Person) {
        store.append(person)
    }
    
    func getPerson(name: String) -> Person? {
        if let nameAtIndex = store.firstIndex(where: {$0.name == name}) {
            return store[nameAtIndex]
        }
        
        return nil
    }
    
    func insertPerson(person: Person) {
        store.append(person)
    }
}

/// Form to allow adding people.
struct AddPerson: View {
    @EnvironmentObject var personStore: PersonStore
    
    @State private var name: String = ""
    @State private var age: String = ""
    @State private var country: LegalAge = LegalAge.UK
    
    var body: some View {
        NavigationView {
            Form {
                TextField("Name", text: $name)
                TextField("Age", text: $age)
                Picker(selection: $country, label: Text("Country")) {
                        ForEach(LegalAge.allCases, id: \.self) { c in
                            Text("\(c.name)").tag(c)
                        }
                }
                Section {
                    Button(action: { personStore.addPerson(person: Person(name: name, age: Int(age) ?? 18, country: country))}, label: { Text("Add person" )})
                }
            }
        }
    }
}

/// View to show peoples details and allow some of these details to be edited.
struct ShowAge: View {
    @ObservedObject var person: Person
    @State private var showAgeEditor = false
    
    var body: some View {
        VStack {
            /// Show the current age.
            Text("\(self.person.name)")
            Text("\(self.person.model.personsAge)")
            Text("\(self.person.model.country)")
            Image(systemName: "keyboard")
            .onTapGesture {
                self.showAgeEditor = true
            }
            /// Present the sheet to update the age.
            .sheet(isPresented: $showAgeEditor) {
                SheetView(showAgeEditor: self.$showAgeEditor)
                .environmentObject(self.person)
                .frame(minWidth: 300, minHeight: 400)
            }
        }
    }
}

/// Sheet to allow editing of persons details.
struct SheetView: View {
    @EnvironmentObject var person: Person
    @Binding var showAgeEditor: Bool

    let maxAge = 21
    let minAge = 16
    
    var body: some View {
        return VStack(alignment: .leading) {
            Text("Name: \(person.name)")
            Stepper(value: $person.model.personsAge, in: minAge...maxAge, step: 1) {
                Text("Age: \(person.model.personsAge)")
            }
            Text("Country: \(person.model.country)")
        }
    }
}

/// ViewModel that creates the Person Object to stored.
class Person: ObservableObject, Identifiable {
    @Published var model: LegalDrinkingAge
    var name: String
    var id = UUID()
    
    init(name: String, age:Int, country: LegalAge) {
        self.name = name
        self.model = Person.createLegalAge(drinkingAge: country.rawValue, country: "\(country)", personsAge: age)
    }
    
    private static func createLegalAge(drinkingAge: Int, country: String, personsAge: Int) -> LegalDrinkingAge {
        LegalDrinkingAge(drinkingAge: drinkingAge, country: country, personsAge: personsAge)
    }
    
    func updateAge(_ age: Int) {
        model.personsAge = age
    }
}

struct LegalDrinkingAge {
    var drinkingAge: Int = 0
    var country: String = ""
    var personsAge: Int = 0
    
    mutating func setDrinkingAge(age: Int, country: String, personsAge: Int) {
        drinkingAge = age
        self.country = country
        self.personsAge = personsAge
    }
}


Separate view and make it observed for person

class StorePersonRowView: View {
   @ObservedObject person: Person

   var body: some View {
     HStack {
        Text("\(person.name)")
        Text("\(person.model.personsAge)")
        Text("\(person.model.country)")
        Image(systemName: (person.model.personsAge >= person.model.drinkingAge) ? "checkmark.shield.fill" : "xmark.shield.fill").foregroundColor((person.model.personsAge >= person.model.drinkingAge) ? Color.green : Color.red)
     }
   }
}

and use it in link

ForEach(personStore.store) { person in
    NavigationLink(destination: ShowAge(person: person)) {
       StorePersonRowView(person: person)
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM