防止 SwiftUI 重置 @State

huangapple go评论120阅读模式
英文:

Prevent SwiftUI from resetting @State

问题

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

  1. import SwiftUI
  2. struct ContentView: View {
  3. @State private var isView1Active = true
  4. let view1 = View1()
  5. let view2 = View2()
  6. var body: some View {
  7. VStack {
  8. if isView1Active {
  9. view1
  10. } else {
  11. view2
  12. }
  13. Button(action: {
  14. self.isView1Active.toggle()
  15. }, label: {
  16. Text("TAP")
  17. })
  18. }
  19. }
  20. }
  21. struct View1: View {
  22. @State private var text = ""
  23. var body: some View {
  24. TextField("View1: type something...", text: $text)
  25. }
  26. }
  27. struct View2: View {
  28. @State private var text = ""
  29. var body: some View {
  30. TextField("View2: type something...", text: $text)
  31. }
  32. }
  33. struct ContentView_Previews: PreviewProvider {
  34. static var previews: some View {
  35. ContentView()
  36. }
  37. }

我希望两个 TextField 保持其内容,但是如果你运行这个示例,会出现一些奇怪的行为:

  • 如果你只在 预览 中运行示例,那么只有 View1TextField 内容会保留。

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

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

英文:

I don't often understand when SwiftUI resets the state of a view (i.e. all that is marked with @State). For example, take a look at this minimum example:

  1. import SwiftUI
  2. struct ContentView: View {
  3. @State private var isView1Active = true
  4. let view1 = View1()
  5. let view2 = View2()
  6. var body: some View {
  7. VStack {
  8. if isView1Active {
  9. view1
  10. } else {
  11. view2
  12. }
  13. Button(action: {
  14. self.isView1Active.toggle()
  15. }, label: {
  16. Text("TAP")
  17. })
  18. }
  19. }
  20. }
  21. struct View1: View {
  22. @State private var text = ""
  23. var body: some View {
  24. TextField("View1: type something...", text: $text)
  25. }
  26. }
  27. struct View2: View {
  28. @State private var text = ""
  29. var body: some View {
  30. TextField("View2: type something...", text: $text)
  31. }
  32. }
  33. struct ContentView_Previews: PreviewProvider {
  34. static var previews: some View {
  35. ContentView()
  36. }
  37. }

I'd want the two TextField to keep their content, but if you run this example some weird behaviours occur:

  • If you run the example on the preview only the View1 TextField content persists:

防止 SwiftUI 重置 @State

  • If you, instead, run the example on the simulator (or on an actual device) neither the first textfield content, nor the second one persist:

防止 SwiftUI 重置 @State

So, what's happening here? Is there a way to tell SwiftUI not to reset @State for a view? Thanks.

答案1

得分: 4

问题在于每次更改 isView1Active 时都会重新创建 View1View2(因为它使用了 @State,它会重新加载 ContentView 的内容)。

解决方法是将 TextField 的文本属性保留在 ContentView 中,如下所示,并使用 @Binding

  1. struct ContentView: View {
  2. @State private var isView1Active = true
  3. @State private var view1Text = ""
  4. @State private var view2Text = ""
  5. var body: some View {
  6. VStack {
  7. if isView1Active {
  8. View1(text: $view1Text)
  9. } else {
  10. View2(text: $view2Text)
  11. }
  12. Button(action: {
  13. self.isView1Active.toggle()
  14. }, label: {
  15. Text("TAP")
  16. })
  17. }
  18. }
  19. }
  20. struct View1: View {
  21. @Binding var text: String
  22. var body: some View {
  23. TextField("View1: type something...", text: $text)
  24. }
  25. }
  26. struct View2: View {
  27. @Binding var text: String
  28. var body: some View {
  29. TextField("View2: type something...", text: $text)
  30. }
  31. }

在操作中显示如下:

防止 SwiftUI 重置 @State

英文:

The issue is that View1 and View2 are being recreated every time isView1Active is changed (because it is using @State which reloads the body of ContentView).

A solution would be to keep the text properties of the TextFields in the ContentView as shown below and use @Binding:

  1. struct ContentView: View {
  2. @State private var isView1Active = true
  3. @State private var view1Text = ""
  4. @State private var view2Text = ""
  5. var body: some View {
  6. VStack {
  7. if isView1Active {
  8. View1(text: $view1Text)
  9. } else {
  10. View2(text: $view2Text)
  11. }
  12. Button(action: {
  13. self.isView1Active.toggle()
  14. }, label: {
  15. Text("TAP")
  16. })
  17. }
  18. }
  19. }
  20. struct View1: View {
  21. @Binding var text: String
  22. var body: some View {
  23. TextField("View1: type something...", text: $text)
  24. }
  25. }
  26. struct View2: View {
  27. @Binding var text: String
  28. var body: some View {
  29. TextField("View2: type something...", text: $text)
  30. }
  31. }

Shown in action:

防止 SwiftUI 重置 @State

答案2

得分: 1

以下是翻译好的部分:

如果 view1view2 完全独立且不包含上下文菜单或底部弹窗,您可以使用 ZStackopacity 的组合。

  1. var body: some View {
  2. VStack {
  3. ZStack {
  4. if isView1Active {
  5. view1.opacity(1)
  6. view2.opacity(0)
  7. } else {
  8. view1.opacity(0)
  9. view2.opacity(1)
  10. }
  11. }
  12. Button(action: {
  13. self.isView1Active.toggle()
  14. }, label: {
  15. Text("点击")
  16. })
  17. }
  18. }
英文:

It view1 and view2 are completely independent and enclosure, like there is no contextmenuor sheet, you may use ZStack and opacity combinations.

  1. var body: some View {
  2. VStack {
  3. ZStack{
  4. if isView1Active {
  5. view1.opacity(1)
  6. view2.opacity(0)
  7. } else {
  8. view1.opacity(0)
  9. view2.opacity(1)
  10. }}
  11. Button(action: {
  12. self.isView1Active.toggle()
  13. }, label: {
  14. Text("TAP")
  15. })
  16. }
  17. }

huangapple
  • 本文由 发表于 2020年1月3日 23:59:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/59581607.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定