防止 SwiftUI 重置 @State。

9

我经常不理解SwiftUI何时重置视图的状态(即所有标记为@State的内容)。例如,看下面这个最简示例:

import SwiftUI

struct ContentView: View {
    @State private var isView1Active = true
    let view1 = View1()
    let view2 = View2()

    var body: some View {
        VStack {
            if isView1Active {
                view1
            } else {
                view2
            }

            Button(action: {
                self.isView1Active.toggle()
            }, label: {
                Text("TAP")
            })
        }
    }
}

struct View1: View {
    @State private var text = ""
    var body: some View {
        TextField("View1: type something...", text: $text)
    }
}

struct View2: View {
    @State private var text = ""
    var body: some View {
        TextField("View2: type something...", text: $text)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

我希望这两个TextField保留它们的内容,但是如果您运行此示例,会出现一些奇怪的行为:
  • 如果您只在预览中运行示例,则仅View1 TextField的内容将保留:

enter image description here

  • 相反地,如果您在模拟器(或实际设备)上运行示例,则第一个文本字段的内容和第二个文本字段的内容都不会保留:

enter image description here

那么发生了什么?有没有办法告诉SwiftUI不要重置视图的@State?谢谢。


为实现持久性,请使用外部视图模型(例如,ObservableObject)。 - Asperi
嗨@Asperi,谢谢。我知道使用ObservableObject可以解决问题,我可能会选择这种方式(即使我真的很想了解为什么SwiftUI在问题示例中重置(at)State)。我认为我们在(at)State管理方面有些遗漏。 - superpuccio
@State 只在视图层次结构内部有效。您创建 View1View2 的方式从 @State 的角度来看是无效的,因此您观察到了问题。实际上,如果您直接将 View1View2 放置在使用 view1view2 变量的位置,您会观察到相同的问题,因为 @State 仅在视图位于视图层次结构中时才起作用。所以,您只是让自己感到困惑。 - Asperi
好的,看起来我的问题的实际答案是"@State只在视图层次结构内部有效",这很遗憾,因为它有一些重要的影响。 - superpuccio
2个回答

4
问题在于每次更改 isView1Active(因为它使用了重新加载ContentView的正文的@State)时,都会重新创建View1View2
一种解决方法是将ContentViewTextField的文本属性保留如下,并使用@Binding:
struct ContentView: View {

    @State private var isView1Active = true
    @State private var view1Text = ""
    @State private var view2Text = ""

    var body: some View {
        VStack {
            if isView1Active {
                View1(text: $view1Text)
            } else {
                View2(text: $view2Text)
            }

            Button(action: {
                self.isView1Active.toggle()
            }, label: {
                Text("TAP")
            })
        }
    }
}

struct View1: View {

    @Binding var text: String

    var body: some View {
        TextField("View1: type something...", text: $text)
    }
}

struct View2: View {

    @Binding var text: String

    var body: some View {
        TextField("View2: type something...", text: $text)
    }
}

实际演示效果如下:

GIF


3
嗨,乔治,谢谢。不幸的是,你所说的是错误的。在我的例子中,“View1”和“View2”并不是每次都重新创建的,因为它们是主视图的“let”属性(您可以通过实现它们的“init()”方法轻松测试它。您会发现它只被调用一次)。这正是我在问题中提出的问题。尽管视图完全相同(未重新创建),但@State却以某种方式重置了。 SwiftUI何时重置@State变量?为什么?有没有办法控制它? - superpuccio
1
@superpuccio 我明白你的意思,但我不确定为什么会这样。但这仍然看起来像是解决你遇到的问题的方法。 - George
1
@superpuccio,George_E 的回答是正确的。@ State 变量在渲染之间不会被保留。因此,即使您在内存中拥有 @ State,一旦视图从视图堆栈中移除,这些状态变量就会被重置。您必须使用 @ Binding。 - Peter Suwara
我知道这篇文章已经接近2年了...但是当我说“每次重新创建”时,更确切地说,视图body正在被重新计算,因此init不会被多次调用。Peter上面的评论是正确的。 - George

1

如果view1view2是完全独立的并且没有上下文关联,例如没有contextmenu或者sheet,那么您可以使用ZStackopacity的组合来实现。

 var body: some View {
    VStack {
        ZStack{
        if isView1Active {
            view1.opacity(1)
             view2.opacity(0)
        } else {
            view1.opacity(0)
            view2.opacity(1)

        }}

        Button(action: {
            self.isView1Active.toggle()
        }, label: {
            Text("TAP")
        })
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接