在学习 SwiftUI 时,我正在构建一个简单的 iOS 应用程序,其中包含一个包含项目数组的模型。可以创建和编辑这些项目。
主视图列出了项目和用于创建新项目的
+
按钮。点击一个项目打开另一个视图以编辑工作表中的项目。这是通过绑定 editItem
发生的,这是一个 @State
使其可变(基于 this answer)。
当我尝试在项目创建后立即打开编辑视图时出现问题:创建新项目时代码在运行时(iPhone 14 Pro 模拟器)失败:
Swift/ContiguousArrayBuffer.swift:600: Fatal error: Index out of range
我已将应用程序归结为以下最小示例:
import SwiftUI
struct Model {
var items: [Item]
}
struct Item: Identifiable {
var id: String
}
struct MainView: View {
@Binding var model: Model
@State var editItem: Binding<Item>?
var body: some View {
VStack {
List($model.items) { $item in
ItemCardView(item: $item.wrappedValue)
.onTapGesture {
editItem = $item
}
}
Button(action: {
model.items.append(Item(id: "New item"))
editItem = $model.items.last // WORKS WITHOUT THIS
}) {
Image(systemName: "plus")
}
}.sheet(item: $editItem) { $item in
let _=print($item)
ItemEditView(item: $item)
}
}
}
struct ItemCardView: View {
var item: Item
var body: some View {
Text(item.id)
}
}
struct ItemEditView: View {
@Binding var item: Item
@State private var editingItem: Item = Item(id: "dummy")
var body: some View {
TextField("Title", text: $editingItem.id)
.onAppear {
editingItem = item
}
.onDisappear {
item = editingItem
}
}
}
@main
struct MyApp: App {
@State var model = Model(items: [])
var body: some Scene {
WindowGroup {
MainView(model: $model)
}
}
}
该应用程序无需线路即可运行
editItem = $model.items.last // WORKS WITHOUT THIS
如果删除此行,
+
按钮将创建一个新项目,您可以通过点击它进行编辑。
我无法查明调试器中的故障,因为我的代码中没有触发异常:传递给
ItemEditView
的绑定在打印时看起来很好(尽管它打印了两次,我不知道是否这是一个提示)。
我既有兴趣了解为什么我的方法不起作用,也有兴趣提出解决潜在问题的更好方法。
我在 M1 笔记本电脑上使用 XCode 14.3,目标是 iOS 16.4。
这一切都归结为@State 和@StateObject 之间的区别。 SwiftUI 的 State 属性包装器是为当前视图本地的简单数据而设计的,但它并不是为了在视图之间共享而设计的,因此仅在视图内使用,并且也应该在视图内初始化。它应该存储简单数据(值类型基本上如 Apple 所声称的那样)。另一方面,我们将 StateObject 用作引用类型的真实来源 - 因此可以作为引用传递并且不会被复制。 潜在的解决方案是:
@main
struct BattlePlanApp: App {
@StateObject var model = Model()
var body: some Scene {
WindowGroup {
MainView(model: model)
}
}
}
class Model: ObservableObject {
var items: [Item] = []
}
struct MainView: View {
@State var model: Model
@State var editItem: Binding<Item>?
var body: some View {
VStack {
List($model.items) { $item in
ItemCardView(item: $item.wrappedValue)
.contentShape(Rectangle())
.onTapGesture {
editItem = $item
}
}
Button(action: {
model.items.append(Item(id: UUID(), title: "New item"))
editItem = $model.items.last
}) {
Image(systemName: "plus")
}.frame(width: 40, height: 40)
}.sheet(item: $editItem) { item in
ItemEditView(item: item)
}
}
}
阅读
https://developer.apple.com/documentation/swiftui/state https://developer.apple.com/documentation/combine/observableobject