Swift @State变量的值不会更改

Swift @State变量的值不会更改,swift,swiftui,Swift,Swiftui,我创建了一个视图,它提供了一个方便的保存按钮和保存方法。然后可以在父视图中使用这两种视图。 我们的想法是提供这些功能,以便可以自定义导航栏项,但保留原始实现 在视图中有一个绑定到@State变量的Textfield。如果从同一视图中调用save方法,则一切都会按预期工作。如果父视图在子视图上调用save方法,则不会应用对@State变量的更改 这是SwiftUI中的一个bug,还是我遗漏了什么?我已经创建了一个简单的playbook实现来演示这个问题 谢谢你的帮助 导入快捷界面 导入Playgr

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

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

这是SwiftUI中的一个bug,还是我遗漏了什么?我已经创建了一个简单的playbook实现来演示这个问题

谢谢你的帮助

导入快捷界面
导入PlaygroundSupport
结构ContentView:视图{
//创建子视图以使保存按钮在此视图中可用
var child=child()
变量体:某些视图{
导航视图{
导航链接(
目的地:child.navigationBarItems(
//将尾部按钮设置为子视图中的按钮。
//这是必需的,因为此视图可能位于模态内部
//工作表,我们需要添加取消按钮作为前导
//按钮:
//前导:self.cancel按钮
尾随:child.saveButton
)  
) {  
文本(“打开”)
}  
}  
}  
}  
结构子级:视图{
//存储textfield中的值
@国家私有var value=“默认”
//使此按钮在此视图和父视图中可用。
//这样可以确保此按钮的可见性始终相同。
var saveButton:某些视图{
按钮(操作:保存){
文本(“保存”)
}  
}  
变量体:某些视图{
VStack{
//允许更改字符串的简单文本字段。
文本字段(“值”,文本:$Value)
//只是为了便于操场更改值。
//通常是通过键盘输入来改变的。
按钮(操作:{
self.value=“新值”
}) {  
文本(“更新”)
}  
}  
}  
func save(){
//这始终显示状态变量的默认值。
//即使在使用了更新按钮之后,内部的值也发生了变化
//文本字段。
打印(“\(值)”)
}  
}  
PlaygroundPage.current.setLiveView(ContentView())

内部
子项
可变的,因为它是用
@State
包装的

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

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


祝你好运。

子视图需要将其状态保持为绑定状态。这项工作:

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())

TextField仅在按下return按钮时更新值绑定。要获取编辑期间发生的文本更改,请使用didSet在子对象上设置观察对象。这是我根据你的例子改变的操场

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())

我认为更快捷的方法是:

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)")
  }
}
基于@nine stones(谢谢!)中的这一点,我实现了一种更快捷的方法来解决我的问题。它不允许像我计划的那样定制导航项,但这不是需要解决的问题。我想在导航链接和模式表中使用
子视图。问题是如何执行自定义取消操作。这就是为什么我删除了按钮实现,并将其替换为
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())

这不会改变结果,虽然我知道它是不可变的,但更改绑定的上下文应该是
子视图。将
var child
更改为
@State
不幸没有任何区别。谢谢@jnblanchard。我可以证实这确实有效。你有关于原因的其他信息吗?如果我将
文本(“\(名称)”)
放入我的代码中(在
文本字段
之后),它会在我键入另一个字母后立即更新。此外,返回键似乎不会影响代码的任何更改。我使用了一个可观察对象将视图与文本数据连接起来。可观察对象上有一个属性观察者(didSet),负责文本值的更改。奇怪的是,在使用swiftui时,我通常会注意到在按下return键后值会更新。但我记得,除非我使用的是设备,而不是模拟器,否则它不会工作。很抱歉不清楚。我可以看到你是如何改变它的,以及为什么它会起作用。我的问题是,如果您有任何关于
@State
实现不起作用的线索的话,我想问的更多。这主要是因为State将提供一个不可变的包装器。要真正了解这个值,您需要打开绑定。我还没有尝试过这个,但是通过在你的状态中使用wrappedValue属性,你可能会得到更改后的值。这确实如预期的那样有效。不幸的是,它向外部视图公开了很多样板文件,而外部视图只是用来修改导航栏项。您是否有任何其他信息说明此操作的原因?我认为您分配为navigationBarItem的saveButton已被捕获,包括它的操作对于您的流程来说太早了。太多的陈词滥调:嗯,我的回答是有点假设t