在 SwiftUI 中更新/编辑数组元素的最佳方法

Posted

技术标签:

【中文标题】在 SwiftUI 中更新/编辑数组元素的最佳方法【英文标题】:Best Way to Update/Edit an Array Element in SwiftUI 【发布时间】:2020-05-30 17:28:50 【问题描述】:

我有一组可识别的培训元素。每个 Training 元素只有两个属性,name 和 isRequired。

将数组元素的现有值更新为新的编辑值的最直接(最快捷)的方法是什么...编辑后的值随后将提交到数据库。

是否可以将EditTraining 视图(子)中的状态设置为传入的(父)值,然后在子视图中编辑状态?

我被困在这个问题上的时间比我承认的要长。

非常感谢您的帮助!

代码如下:

import SwiftUI

struct Training: Identifiable 
    let id: String
    let trainingName: String
    let isRequired: Bool


class GetTrainings: ObservableObject 
    @Published var items = [Training]()

    init() 
        self.items = [
            Training(id: "ttt1", trainingName: "Safety", isRequired: true),
            Training(id: "ttt2", trainingName: "Administrative", isRequired: false),
            Training(id: "ttt3", trainingName: "Computer", isRequired: true),
            Training(id: "ttt4", trainingName: "People", isRequired: true),
            Training(id: "ttt5", trainingName: "Managerial", isRequired: true),
        ]
    


struct TrainingList: View 

    @ObservedObject var trainings = GetTrainings()

    var body: some View 
        NavigationView 
            VStack 
                List 

                    ForEach(trainings.items)  training in

                        HStack 
                            NavigationLink(destination: TrainingView(training: training)) 
                                Text("\(training.trainingName)")
                            
                        
                    

                
            .navigationBarTitle("Training List")
        
    


struct TrainingView: View 

    var training: Training

    var body: some View 

        VStack 

            Text("\(training.trainingName)").font(.body)
            Text("\(training.isRequired == true ? "Required Training" : "Training Not Required")")

            HStack 
                NavigationLink(destination: EditTraining(training: training)) 
                    Text("Edit Training Details")
                
            
        .navigationBarTitle("\(training.trainingName) Page", displayMode: .inline)

    


struct EditTraining: View 

    var training: Training

    // Can I set the state values to the passed in values ???
    @State private var newName: String = ""
    @State private var isRequiredTraining: Bool = false
    //@Binding var name: String = training.trainingName ????

    private func submitData() 

        let newName = self.newName
        let newBoolVal = self.isRequiredTraining

        print("Firebase Sync Id is :\(training.id) Text: \(newName) and Bool: \(newBoolVal)")

    

    var body: some View 
        VStack 
            Form 
                Section (header: Text("Edit"))  

                    Text("\(training.trainingName)")
                    /* TextField should Populate With passed In Training Name Here*/
                    TextField("New Name", text: self.$newName)
                    Toggle(isOn: self.$isRequiredTraining) 
                        Text("Is Required")
                    
                

                Section 

                    Button(action: 
                        self.submitData()
                    ) 
                        Text("Submit")
                    

                
            
        .navigationBarTitle("Edit Training Page", displayMode: .inline)
    


struct ContentView: View 

    var body: some View 

        VStack 

            TrainingList()

        
    


struct ContentView_Previews: PreviewProvider 
    static var previews: some View 
        ContentView()
    

【问题讨论】:

【参考方案1】:

在我看来,对模型使用 ObservableObject 更为可取,因为它允许通过引用模型对象深入层次结构并在工作流程中保持最新状态。

这里有一个解决方案。使用 Xcode 11.4 / ios 13.4 测试

class Training: ObservableObject, Identifiable 
    let id: String
    @Published var trainingName: String
    @Published var isRequired: Bool

    init(id: String, trainingName: String, isRequired: Bool) 
        self.id = id
        self.trainingName = trainingName
        self.isRequired = isRequired
    


class GetTrainings: ObservableObject 
    @Published var items = [Training]()

    init() 
        self.items = [
            Training(id: "ttt1", trainingName: "Safety", isRequired: true),
            Training(id: "ttt2", trainingName: "Administrative", isRequired: false),
            Training(id: "ttt3", trainingName: "Computer", isRequired: true),
            Training(id: "ttt4", trainingName: "People", isRequired: true),
            Training(id: "ttt5", trainingName: "Managerial", isRequired: true),
        ]
    


struct TrainingList: View 

    @ObservedObject var trainings = GetTrainings()

    var body: some View 
        NavigationView 
            VStack 
                List 

                    ForEach(trainings.items)  training in

                        HStack 
                            NavigationLink(destination: TrainingView(training: training)) 
                                Text("\(training.trainingName)")
                            
                        
                    

                
            .navigationBarTitle("Training List")
            .onAppear 
                self.trainings.objectWillChange.send() // refresh
            
        
    


struct TrainingView: View 

    @ObservedObject var training: Training

    var body: some View 

        VStack 

            Text("\(training.trainingName)").font(.body)
            Text("\(training.isRequired == true ? "Required Training" : "Training Not Required")")

            HStack 
                NavigationLink(destination: EditTraining(training: training)) 
                    Text("Edit Training Details")
                
            
        .navigationBarTitle("\(training.trainingName) Page", displayMode: .inline)

    


struct EditTraining: View 

    @ObservedObject var training: Training

    @State private var newName: String
    @State private var isRequiredTraining: Bool

    init(training: Training) 
        self.training = training
        self._newName = State(initialValue: training.trainingName)
        self._isRequiredTraining = State(initialValue: training.isRequired)
    

    private func submitData() 

        let newName = self.newName
        let newBoolVal = self.isRequiredTraining

        print("Firebase Sync Id is :\(training.id) Text: \(newName) and Bool: \(newBoolVal)")

        self.training.trainingName = newName
        self.training.isRequired = newBoolVal
    

    var body: some View 
        VStack 
            Form 
                Section (header: Text("Edit"))  

                    Text("\(training.trainingName)")
                    /* TextField should Populate With passed In Training Name Here*/
                    TextField("New Name", text: self.$newName)
                    Toggle(isOn: self.$isRequiredTraining) 
                        Text("Is Required")
                    
                

                Section 

                    Button(action: 
                        UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder),
                            to:nil, from:nil, for:nil)
                        self.submitData()
                    ) 
                        Text("Submit")
                    

                
            
        .navigationBarTitle("Edit Training Page", displayMode: .inline)
    

【讨论】:

你是救生员!我不确定我是否会提出您的解决方案。它工作完美!非常感谢! 这是一个很好的例子,非常有帮助,谢谢。为什么需要按钮中的UIApplication.shared.sendAction?似乎可以正常工作,self.trainings.objectWillChange.send()

以上是关于在 SwiftUI 中更新/编辑数组元素的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章

在 Apple Watch 上的 SwiftUI 中,更新描述自具有 Always On 状态的日期以来的间隔的字符串的最佳方法是啥?

SwiftUI:如何在另一个视图中更新传递的数组项

在Vue中编辑数组的最佳方法。 v型问题

SwiftUI - 删除绑定数组中的元素会导致错误

如何在 Userdefaults (SwiftUI) 中将新元素附加到数组

Swift UI:使用数组中的随机元素更新视图