簡體   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