struct ContentView: View {
@State var items = ["red", "blue", "green"]
var body: some View {
VStack {
ForEach(items.indices, id: \.self) { idx in
ItemView(str: items[idx])
}
Button("Replace items") {
items = [ "pig", "dog", "horse" ]
}
}.frame(width: 200, height: 150)
}
}
struct ItemView: View {
@State var itemstr = ""
let str: String
var body: some View {
HStack {
Text(str)
TextField("New: "+str, text: $itemstr)
}
}
}
输入一些文字。
现在按下“替换项目”按钮。
现在可以看到 TextField @State 并且“str”标签数据不一致。 “pig”来自刷新的视图,“rreedd”来自视图的旧副本。
文本上的退格确认初始值也不一致。
看起来 ItemView 自定义视图正在使用新的初始值设定项参数 (str) 但使用旧状态 (itemstr) 进行重建。
我真正的问题比这更复杂。但它涉及使用 ForEach 创建的子视图,并且在模型数据更改时 ForEach 不会按照我期望的方式重新迭代。
我不确定这是否是 SwiftUI 的错误,因为我没有深入了解 SwiftUI 用于传播视图更改的算法。
好像是创建了一个新视图,将新视图的内部状态与旧视图的内部状态进行比较,然后决定是重新计算子视图并使用新视图还是坚持使用旧视图。但我从未在官方文档中看到过这一点。
答案 0 :(得分:2)
这种行为是设计使然。
在 SwiftUI 中,视图会定期初始化(每当它们的父级重新计算其 body
时),并且状态决定了视图是否实际发生了变化(这意味着它也需要反过来,重新计算它自己的 body
)。
因此,当您声明 @State var itemstr = ""
时,您给该状态属性一个 initial 值 ""
,但随后视图在初始化之间保持其状态。 >
那你如何重置状态?
在您的示例中,我认为,每当 ItemView
更改时,str
都会想要重置其状态。您需要使用 onChange
:
struct ItemView: View {
@State var itemstr = ""
let str: String
var body: some View {
HStack {
Text(str)
TextField("New: "+str, text: $itemstr)
}
.onChange(of: str, perform: { _ in
itemstr = "" // reset whenever str changes
})
}
}
或者,虽然不一定适合这个例子,但父级可以通知 SwiftUI 它的主体,尽管视图结构看起来没有变化,但实际上已经完全改变,状态应该被重置。
在这种情况下,您可以使用 .id
视图修饰符并提供 items[idx]
作为每个项目的 id
- 这意味着当 items[idx]
更改时意味着视图它下面的层次结构也发生了变化。
采用这种方法,您只需更新父级,而可以保持 ItemView
原样:
ForEach(items.indices, id: \.self) { idx in
ItemView(str: items[idx])
.id(items[idx])
}