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