view - 为什么我的 SwiftUI 自定义视图没有正确更新?
问题描述
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”来自视图的旧副本。
Backspace 确认的文本初始值也不一致。
看起来 ItemView 自定义视图正在使用新的初始化参数 (str) 但使用旧状态 (itemstr) 进行重建。
我真正的问题比这更复杂。但它涉及使用 ForEach 创建的子视图,并且当模型数据发生更改时,ForEach 不会按照我期望的方式重新迭代。
我不确定这是否是 SwiftUI 错误,因为我不深入了解 SwiftUI 用于传播视图更改的算法。
似乎它创建了一个新视图,将新视图的内部状态与旧视图的内部状态进行比较,然后决定是重新计算子视图并使用新视图还是坚持旧视图。但我从未在官方文档中看到过这一点。
解决方案
此行为是设计使然。
在 SwiftUI 中,视图非常定期地初始化(每当它们的父级重新计算它的 时body
),并且它是决定视图是否实际发生变化的状态(这意味着它也需要反过来重新计算它自己的body
)。
因此,当您声明 时@State var itemstr = ""
,您为该状态属性赋予初始值""
,但是视图在初始化之间保持其状态。
那你如何重置状态?
在您的示例中,我认为,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])
}
推荐阅读
- angular - 当我刷新页面时,我返回主页
- swift - 如何使用 Metal Framework 在 iOS Swift 中绘制文本
- jquery - 如何选择具有特定类且以类结尾的元素?
- php - 使用队列生成 pdf 有时会显示旧设计,有时会显示新设计
- mongodb - 数据库中不那么紧急的字段我应该填写什么?
- mysql - mariadb-server 已被 mysql-community-server 淘汰
- typescript - TypeScript 条件类型扩展对象,仅可用属性
- gis - 如何在 QGIS 中合并来自两个栅格图层的数据?
- timer - Arduino P10 显示和时间计数
- wso2 - WSO2 Ei 代理服务和 DS