【问题标题】:SwiftUI state over multiple views多个视图上的 SwiftUI 状态
【发布时间】:2020-11-08 18:20:13
【问题描述】:

在 SwiftUI 中,首先处理应用程序的状态似乎非常简单。基本上有两个属性包装器:@EnvironmentObject@ObservedObject(和 @State)。但事实证明,事情并没有那么简单。

我在这里发布了几个问题。他们中的大多数都得到了很好的回答: SwiftUI onDelete List with ToggleSwiftUI ForEach with .indices() does not update after onDeleteSwiftUI: Index out of range when deleting cells with toggle

但每次我走得更远,一些主要问题仍然存在。这些是

  1. 通过NavigationLinkListForEach 将可绑定变量移交给子视图,并将更改反映回主视图。 (我尝试了几种解决方案,例如在阵列上使用 .indices 并动态绑定 (SwiftUI onDelete List with Toggle))。 为了反映更改,我还尝试使用 .onReceive(),但也没有成功。
  2. 当我尝试使用以下公式时,我总是遇到索引问题:someArray.firstIndex(where {condition})!。你可以在 Apple 的 SwiftUI 教程中找到它。当我在ForEach-closure 中使用索引然后尝试使用.onDelete() 滑动删除时,我也遇到了问题。错误信息是:
Thread 1: Fatal error: Index out of range
  1. 如何将变量绑定到主视图后面两层或多层的@EnvironmentObject-model?使用 2. 中的公式会产生非常长的变量名。这似乎不对。

这是一个例子,它几乎可以工作。唯一不起作用的是当我更改SubViews 中的值时更新MainView。但我觉得在我的应用中使用多个 ObservedObjects 并不合适。

class Model: ObservableObject {
    @Published var items: [Item]
    
    init(name: String, items: [Item]) {
        self.items = items
    }   
}

class Item: Identifiable, ObservableObject {
    var id = UUID()
    var itemName: String
    @Published var subItems: [SubItem]
    
    init(itemName: String, payments: [SubItem]) {
        self.itemName = itemName
        self.subItems = payments
    }
}

class SubItem: Identifiable, ObservableObject {
    var id = UUID()
    var isOn: Bool
    
    init(isOn: Bool) {
        self.isOn = isOn
    }
}


struct MainView: View {
    @EnvironmentObject var model: Model
    
    var body: some View {
        NavigationView {
            List {
                ForEach(model.items) {item in
                    NavigationLink(destination: SubView1(item: item)) {
                        HStack{
                            Text(item.itemName)
                            Text(item.subItems[0].isOn ? "True":"False") // I know: bad, but only for quick debugging reasons
                            Text(item.subItems[1].isOn ? "True":"False")
                        }
                    }.onReceive(item.objectWillChange, perform: {self.model.objectWillChange.send()})
                }.onDelete(perform: delete)
            }
        }
    }
    
    func delete(at offsets: IndexSet) {
        self.model.items.remove(atOffsets: offsets)
    }
    
}

struct SubView1: View {
    @ObservedObject var item: Item
    var body: some View {
        List {
            ForEach(item.subItems) {subItem in
                NavigationLink(destination: SubView2(subItem: subItem)) {
                    ToggleView(subItem: subItem)
                }
                
            }.onDelete(perform: delete)
        }
    }
    
    func delete(at offsets: IndexSet) {
           self.item.subItems.remove(atOffsets: offsets)
       }
}

struct SubView2: View {
    @ObservedObject var subItem: SubItem
    var body: some View {
        Toggle(isOn: $subItem.isOn) {
            Text("Toggle-Text")
        }
    }
}


struct ToggleView: View {
    @ObservedObject var subItem: SubItem
    var body: some View {
        Toggle(isOn: $subItem.isOn) {
            Text("Toggle-Text")
        }
    }
}

【问题讨论】:

  • 在我看来,您应该在您的MainView 中使用一个@State struct/@ObservableObject 类,然后将@Bindings 传递给子视图
  • 首先,您的视图中的删除方法应该在您的视图模型(@ObservableObjects)中,或者您可以在子视图中使用@Binding,我想您在某处有一个模型变量,以便您传递它到您的 MainView。

标签: list foreach model binding swiftui


【解决方案1】:

您可以做的是访问一个项目的绑定并在ToggleView中使用它:

struct SubView1: View {
    @ObservedObject var item: Item
    var body: some View {
        List {
            ForEach(0..<item.subItems.count, id: \.self) { index in
                NavigationLink(destination: SubView2(subItem: self.$item.subItems[index])) { // <- pass the binding
                    ToggleView(subItem: self.$item.subItems[index]) // <- pass the binding
                }
            }.onDelete(perform: delete)
        }
    }
    ...
}
struct SubView2: View {
    @Binding var subItem: SubItem // <- receive the binding
    var body: some View {
        Toggle(isOn: $subItem.isOn) {
            Text("Toggle-Text")
        }
    }
}
struct ToggleView: View {
    @Binding var subItem: SubItem // <- receive the binding
    var body: some View {
        Toggle(isOn: $subItem.isOn) {
            Text("Toggle-Text")
        }
    }
}

【讨论】:

  • 你好 pawello2222。我尝试了您的解决方案,但没有奏效。 1. 当我更改SubView2 的值时,MainView 不会更改。 2.当我删除SubView1上的一行时,我收到错误消息:Thread 1: Fatal error: Index out of rangein the AppDelegate
  • @Nico 我为ToggleView 提出的解决方案也可以应用于SubView2。我更新了我的答案以反映这一点。 Index out of range 发生是因为您删除了一个项目,但在主视图中您仍然尝试访问它:Text(item.subItems[1].isOn ? "True" : "False") - 要使这条线工作,您需要至少两个子项。
猜你喜欢
  • 1970-01-01
  • 2022-11-11
  • 1970-01-01
  • 2021-08-21
  • 2022-10-19
  • 2020-10-05
  • 2021-05-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多