SwiftUI如何在更新已发布数组中的对象而不是数组本身时更新视图

Posted

技术标签:

【中文标题】SwiftUI如何在更新已发布数组中的对象而不是数组本身时更新视图【英文标题】:SwiftUI how to update a view when the object in a published array is updated and not the array itself 【发布时间】:2020-07-29 14:49:01 【问题描述】:

以下是我面临的问题的人为示例。

情况: 我有一个类 PersonStore,它将类 Person 存储在一个数组中并已发布。 Person 类为每个 Person 创建一个 LegalAge 模型,该模型已发布 问题: 当更新 LegalAge 中的人员年龄时,不会更新 PersonStore 的视图。

我猜测没有变化的原因是因为变化发生在数组中的对象而不是数组本身。我该如何克服这个问题?


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
    



【问题讨论】:

【参考方案1】:

分离视图并为人观察

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)
     
   

并在链接中使用它

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

【讨论】:

以上是关于SwiftUI如何在更新已发布数组中的对象而不是数组本身时更新视图的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI 将数据传递给子视图

如何切换数组中对象的属性并更新 SwiftUI 视图?

Swiftui 列表更新问题

获取嵌套数组 Firebase + SwiftUI

在内容视图 SwiftUI 中更新对象数组

SwiftUI 视图不会为具有子类的已发布对象更新