Значение из переменной @State не изменяется - PullRequest
1 голос
/ 03 февраля 2020

Я создал вид, который предоставляет удобную кнопку сохранения и метод сохранения. Оба могут быть использованы внутри родительского представления. Идея состоит в том, чтобы предоставить их так, чтобы элементы панели навигации можно было настраивать, но сохранить исходную реализацию.

Внутри представления есть одно текстовое поле, которое связано с переменной @State. Если метод save вызывается из того же представления, все работает как положено. Если родительское представление вызывает метод save для дочернего представления, изменения в переменной @State не применяются.

Это ошибка в SwiftUI или я что-то упустил? Я создал простую реализацию playbook, которая демонстрирует проблему.

Спасибо за вашу помощь.

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

Ответы [ 5 ]

1 голос
/ 04 февраля 2020

TextField будет обновлять привязку вашего значения только при нажатии кнопки возврата. Чтобы получить изменения текста, которые происходят во время редактирования, настройте наблюдаемый объект в Child с 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())
0 голосов
/ 07 февраля 2020

На основании этой похвалы от @ nine-stone (спасибо!) Я реализовал более 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())
0 голосов
/ 06 февраля 2020

Я думаю более 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)")
  }
}
0 голосов
/ 04 февраля 2020

Child представление должно сохранять свое состояние как @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())
0 голосов
/ 03 февраля 2020

Внутри Child: value является изменяемым , потому что оно обернуто с @State.

Внутри ContentView: child является неизменный , поскольку он не обернут с @State.

Ваш вопрос может быть исправлен с помощью этой строки: @State var child = Child()

Удачи.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...