【问题标题】:Why is my SwiftUI custom view not updated correctly?为什么我的 SwiftUI 自定义视图没有正确更新?
【发布时间】:2021-09-26 13:02:51
【问题描述】:
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 用于传播视图更改的算法。

好像它创建了一个新视图,将新视图的内部状态与旧视图的内部状态进行比较,然后决定是重新计算子视图并使用新视图还是坚持旧视图。但我从未在官方文档中看到过这样的说明。

【问题讨论】:

  • 也许在ItemView 中使用@Binding 而不是@State
  • @aheze,你可以直接绑定,但是这种方法通常用于子视图想要保持其“未保存”状态而不直接更新父视图的情况
  • @NewDev 啊,我明白了,很好的答案!

标签: view swiftui state


【解决方案1】:

这种行为是设计使然。

在 SwiftUI 中,视图被定期初始化(每当它们的父级重新计算其body),并且它是确定视图是否实际发生变化的状态(这意味着它也需要反过来重新计算其拥有body)。

因此,当您声明 @State var itemstr = "" 时,您为该状态属性赋予了一个 initial"",但随后视图在初始化之间保持其状态。


那你如何重置状态呢?

在你的例子中,我认为,每当str 发生变化时,ItemView 想要重置其状态。您需要使用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])
}

【讨论】:

    猜你喜欢
    • 2021-08-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多