繁体   English   中英

@State 变量的值不会改变

[英]Value from @State variable does not change

我创建了一个视图,它提供了一个方便的保存按钮和一个保存方法。 然后两者都可以在父视图中使用。 想法是提供这些以便可以自定义导航栏项目,但保留原始实现。

在视图中有一个绑定到 @State 变量的文本字段。 如果从同一视图中调用 save 方法,一切都会按预期工作。 如果父视图调用子视图的保存方法,则不会应用对@State 变量的更改。

这是 SwiftUI 中的错误,还是我遗漏了什么? 我创建了一个简单的剧本实现来演示这个问题。

谢谢您的帮助。

import SwiftUI  
import PlaygroundSupport  

struct ContentView: View {  
    // Create the child view to make the save button available inside this view  
    var child = Child()  

    var body: some View {  
        NavigationView {  
            NavigationLink(  
                destination: child.navigationBarItems(  
                    // Set the trailing button to the one from the child view.  
                    // This is required as this view might be inside a modal  
                    // sheet, and we need to add the cancel button as a leading  
                    // button:  
                    // leading: self.cancelButton  
                    trailing: child.saveButton  
                )  
            ) {  
                Text("Open")  
            }  
        }  
    }  
}  

struct Child: View {  
    // Store the value from the textfield  
    @State private var value = "default"  

    // Make this button available inside this view, and inside the parent view.  
    // This makes sure the visibility of this button is always the same.  
    var saveButton: some View {  
        Button(action: save) {  
            Text("Save")  
        }  
    }  

    var body: some View {  
        VStack {  
            // Simple textfield to allow a string to change.  
            TextField("Value", text: $value)  

            // Just for the playground to change the value easily.  
            // Usually it would be chnaged through the keyboard input.  
            Button(action: {  
                self.value = "new value"  
            }) {  
                Text("Update")  
            }  
        }  
    }  

    func save() {  
        // This always displays the default value of the state variable.  
        // Even after the Update button was used and the value did change inside  
        // the textfield.  
        print("\(value)")  
    }  
}  

PlaygroundPage.current.setLiveView(ContentView()) 

TextField 只会在按下返回按钮时更新您的值绑定。 要获取编辑期间发生的文本更改,请使用 didSet 在 Child 上设置观察到的 object。 这是我从您的示例中使用的操场。

struct ContentView: View {

  var child = Child()

  var body: some View {
    NavigationView {
      NavigationLink(
        destination: child.navigationBarItems(
          trailing: child.saveButton
        )
      ) {
        Text("Open")
      }
    }
  }
}

class TextChanges: ObservableObject {

  var completion: (() -> ())?
  @Published var text = "default" {
    didSet {
      print(text)
    }
  }
}

struct Child: View {

  @ObservedObject var textChanges = TextChanges()

  var saveButton: some View {
    Button(action: save) {
      Text("Save")
    }
  }

  var body: some View {
    VStack {
      TextField("Value", text: $textChanges.text).multilineTextAlignment(.center)

      Button(action: {
        print(self.textChanges.text)
      }) {
        Text("Update")
      }

    }
  }

  func save() {
    print("\(textChanges.text)")
  }
}

PlaygroundPage.current.setLiveView(ContentView())

我认为更 SwiftUi 的做法是:

import SwiftUI
import PlaygroundSupport

struct ContentView: View {
  var body: some View {
    return NavigationView {
      // tell the child view where to render it's navigation item
      // Instead of configuring navigation items.
      NavigationLink(destination: Child(navigationSide: .left)) {
        Text("Open")
      }
    }
  }
}

struct Child: View {
  enum NavigationSide { case left, right }
  // If you really want to encapsulate all state in this view then @State 
  // is a good choice. 
  // If the parent view needs to read it, too, @Binding would be your friend here
  @State private var value: String = "default"
  // no need for @State as it's never changed from here.
  var navigationSide = NavigationSide.right
  // wrap in AnyView here to make ternary in ui code easier readable.
  var saveButton: AnyView {
    AnyView(Button(action: save) {
      Text("Save")
    })
  }
  var emptyAnyView: AnyView { AnyView(EmptyView()) }
  var body: some View {
    VStack {
      TextField("Value", text: $value)
      Button(action: {
        self.value = "new value"
      }) {
        Text("Update")
      }
    }
    .navigationBarItems(leading: navigationSide == .left ? saveButton : emptyAnyView,
                        trailing: navigationSide == .right ? saveButton : emptyAnyView)
  }

  func save() {
    print("\(value)")
  }
}

Child内部: value可变的,因为它被@State包裹着。

ContentView内部: child不可变的,因为它没有@State包装。

您的问题可以通过以下行解决: @State var child = Child()

祝你好运。

Child视图需要将其 state 保留为@Binding 这有效:

import SwiftUI
import PlaygroundSupport

struct ContentView: View {
  @State var v = "default"
  var body: some View {
    let child = Child(value: $v)
    return NavigationView {
      NavigationLink(
        destination: child.navigationBarItems(trailing: child.saveButton)
      ) {
        Text("Open")
      }
    }
  }
}

struct Child: View {
  @Binding var value: String
  var saveButton: some View {
    Button(action: save) {
      Text("Save")
    }
  }
  var body: some View {
    VStack {
      TextField("Value", text: $value)
      Button(action: {
        self.value = "new value"
      }) {
        Text("Update")
      }
    }
  }
  func save() {
    print("\(value)")
  }
}
PlaygroundPage.current.setLiveView(ContentView())

基于@nine-stones 的这个推荐(谢谢。)我实现了更多 SwiftUI 方式来解决我的问题,它不允许按我的计划自定义导航项。 但这不是需要解决的问题。 我想在导航链接以及模态表中使用Child视图。 问题是如何执行自定义取消操作。 这就是我删除按钮实现并将其替换为cancelAction闭包的原因。 现在我可以随心所欲地显示子视图。

一件事我仍然不知道为什么 SwiftUI 没有将子上下文应用于saveButton方法内的按钮。

不过,这是代码,也许它对将来的人有帮助。

import SwiftUI
import PlaygroundSupport

struct ContentView: View {
    @Environment(\.presentationMode) var presentationMode

    var body: some View {
        NavigationView {
            NavigationLink(
                destination: Child(
                    // Instead of defining the buttons here, I send an optional
                    // cancel action to the child. This will make it possible
                    // to use the child view on navigation links, as well as in
                    // modal dialogs.
                    cancelAction: {
                        self.presentationMode.wrappedValue.dismiss()
                    }
                )
            ) {
                Text("Open")
            }
        }
    }
}

struct Child: View {
    // Store the value from the textfield
    @State private var value = "default"

    @Environment(\.presentationMode) var presentationMode

    var cancelAction: (() -> Void)?

    // Make this button available inside this view, and inside the parent view.
    // This makes sure the visibility of this button is always the same.
    var saveButton: some View {
        Button(action: save) {
            Text("Save")
        }
    }

    var body: some View {
        VStack {
            // Simple textfield to allow a string to change.
            TextField("Value", text: $value)

            // Just for the playground to change the value easily.
            // Usually it would be chnaged through the keyboard input.
            Button(action: {
                self.value = "new value"
            }) {
                Text("Update")
            }
        }
        .navigationBarItems(
            leading: self.cancelAction != nil ? Button(action: self.cancelAction!, label: {
                Text("Cancel")
            }) : nil,
            trailing: self.saveButton
        )
    }

    func save() {
        // This always displays the default value of the state variable.
        // Even after the Update button was used and the value did change inside
        // the textfield.
        print("\(value)")
    }
}

PlaygroundPage.current.setLiveView(ContentView())

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM