[英]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.