我如何从项目的分层列表视图中 update/save 属性 更改子项目

How can I update/save a Property Change for Child Item from a Hierarchical List View of Items

查看以下应用程序屏幕:

内容视图屏幕:

公开了包含分层列表子行的内容视图:

父行详细信息视图:

子行详细视图:

参考上述观点,以下是我执行的步骤以及我试图解决的问题:

  1. 启动应用程序。
  2. 从启动时显示的功能(内容视图)中,看到列表视图中列出了一个项目(1.0 移动容器)
  3. 单击列表项右侧的黄色(我的应用强调色)显示箭头。
  4. 两个从属子列表行出现在父列表项下,1.1 移动位置和 1.2 保持位置。
  5. 当我点击层次结构列表中的父项目 (1.0 Move Vessel) 时,我能够成功导航到该点击项目的详细视图。
  6. 使用 TextEditor 视图在详细视图中编辑点击项目属性的 1.0 Move Vessel 项目(默认为测试)的描述。
  7. 点击详细视图左上角的黄色保存按钮。应用导航回父函数(内容视图)。
  8. 再次单击父 1.0 移动容器行。
  9. 从第 5 步和第 6 步中所做的更改可以看出描述已成功保存并显示。
  10. 对 1.1 移动位置列表行再次重复步骤 5 到 8。
  11. 看到对描述所做的 edit/change 没有保存,而是显示默认的 test1 描述(不是想要的)。
  12. 对 1.2 Hold Position 列表行再次重复步骤 5 到 8。
  13. 看到对描述所做的 edit/change 没有保存,而是显示默认的 test2 描述(不是想要的)。

我想我的保存代码逻辑可能有问题,我正在尝试调查。

这里是详细视图、视图模型和模型的 swift 文件(我没有包含内容视图代码,因为该代码在详细视图中工作正常。同样,我认为问题出在我的保存按钮和用于更新视图模型的函数调用代码中。 注意:抱歉,我似乎无法弄清楚如何在代码视图中获取一个文件的所有代码。我似乎有一些没有出现在代码视图中的右大括号。我认为您仍然可以按照代码进行操作。

struct FunctionDetailView: View {
    @State var vesselFunction: VesselFunction
    @State var vesselFunctionDescription: String
    @Environment(\.presentationMode) var presentationMode
    @EnvironmentObject var functionViewModel : FunctionViewModel
    
    
    var body: some View {
        
        NavigationView {
            
            Form {
                Text("Enter description below")
                TextEditor(text: $vesselFunctionDescription)
                    .frame(height: 200)
                    .toolbar {
                        Button {
                            //print(vesselFunction)
                            
                            vesselFunction.funcDescription = vesselFunctionDescription
                            
                            //print(vesselFunction)
                            
                            functionViewModel.updateVesselFunction(vesselFunction: vesselFunction)
                            
                            //print(vesselFunction)
                            
                            presentationMode.wrappedValue.dismiss()
                        } label: {
                            Text("Save")
                        }
                        
                    }
            }
            .padding()
            .navigationTitle(vesselFunction.name)
            .navigationBarTitleDisplayMode(.inline)
        }
    }
}

struct FunctionDetailView_Previews: PreviewProvider {
    static var previews: some View {
        FunctionDetailView(vesselFunction: VesselFunction(id: UUID(), name: "x.x Verb Noun", funcDescription: "Description", children: nil), vesselFunctionDescription: "placeholder")
            .environmentObject(FunctionViewModel())
            .preferredColorScheme(.dark)
    }
}
    

FunctionViewModel.swift

@MainActor class FunctionViewModel: ObservableObject {
    
    @Published private(set) var decomp : [VesselFunction] = [
        VesselFunction(id: UUID(), name: "1.0 Move Vessel", funcDescription: "test", children: [
            VesselFunction(id: UUID(), name: "1.1 Move Position", funcDescription: "test1", children: nil),
            VesselFunction(id: UUID(), name: "1.2 Hold Position", funcDescription: "test2", children: nil)
        ])
    ]
    
    func updateVesselFunction(vesselFunction: VesselFunction) {
        /*
         if let index = decomp.firstIndex(where: { (existingVesselFunction) -> Bool in
         return existingVesselFunction.id == vesselFunction.id
         }) {
         //run this code
         }
         */
        // cleaner version of above
        if let index = decomp.firstIndex(where: { [=13=].id == vesselFunction.id }) {
            decomp[index] = vesselFunction.updateCompletion()
        }
        /*
         else {
         for item in decomp {
         if item.children != nil {
         if let index = item.children?.firstIndex(where: { [=13=].id == vesselFunction.id }) {
         item.children![index] = vesselFunction.updateCompletion()
         }
         }
         }
         } */
    }
}

FunctionModel.swift

struct VesselFunction: Identifiable {
    let id : UUID
    let name : String
    var funcDescription : String
    var children : [VesselFunction]?
    
    init(id: UUID, name: String, funcDescription: String, children: [VesselFunction]?) {
        self.id = id
        self.name = name
        self.funcDescription = funcDescription
        self.children = children
    }
    
    func updateCompletion() -> VesselFunction {
        return VesselFunction(id: id, name: name, funcDescription: funcDescription, children: children)
    }
}

正如您从 FunctionViewModel 代码底部注释掉的 else 和 for-in 循环代码中看到的那样,我试图查看是否需要执行类似此代码的操作来访问的子 VesselFunction 数组条目分解发布 属性。使用未注释掉的 if let 索引代码,保存功能有效,但仅适用于顶级分解数组 VesselFunction 元素,而不适用于嵌套的子数组元素。

任何帮助将不胜感激,以便在更改 TextEditor 字段并在 FunctionDetailView 中按下保存按钮时更新所有分解数组元素,包括父元素和嵌套子元素。 注意:我只显示了用于分解 属性 的 1 级深度嵌套子数组。我实际上想要多个(至少 3 个)级别的子数组,所以如果您有任何想法如何使 updateVesselFunction 函数适用于多个子数组元素,我将不胜感激。

现在问题不在于您的代码,而在于程序的架构。您确实需要根据 MVVM 概念重新组织应用程序。如果您不确定它们,请研究 Apple’s SwiftUI Tutorials & Stanford’s CS193P。如果没有合适的架构,您就会迷失在兔子洞中,以至于我放弃了修复代码的尝试。

此外,考虑到您的数据结构,我会认真考虑使用 Core Data 对其建模。您的 VesselFunction 结构包含一个 VesselFunction 的数组,并且它更好地建模为一种关系,而不是让一个结构包含一个可以包含相同结构的数组的相同结构的数组。作为结构而不是核心数据来处理是一场噩梦 class.

我也会考虑让你的 FunctionDetailView 只显示数据,并有一个单独的编辑视图。这将使您的视图保持独立并更易于管理。

最后,您的命名约定有很多冗余。从理论上讲,您可能会尝试访问 functionViewModel.funcDescription 处的一段数据(更不用说:functionViewModel.children[index].children[subIndex].children[subSubIndex].funcDescription);这可能会有点笨拙。越往下,情况越糟。

在主视图中使用 ForEach($model.items) { $item in 以便您获得对模型项的写入权限。在详细视图中,将 @State 更改为 @Binding