【问题标题】:How to detect live changes on TextField in SwiftUI?如何在 SwiftUI 中检测 TextField 的实时变化?
【发布时间】:2020-01-12 11:48:33
【问题描述】:

我有一个简单的 TextField,像这样绑定到状态“位置”,

TextField("Search Location", text: $location)

我想在每次这个字段改变时调用一个函数,像这样:

TextField("Search Location", text: $location) {
   self.autocomplete(location)
}

但是这不起作用。我知道有回调,onEditingChanged - 但是这似乎只在字段被聚焦时触发。

如何在每次更新字段时调用此函数?

【问题讨论】:

    标签: ios textfield swiftui


    【解决方案1】:

    我发现最有用的是 TextField 有一个称为 onEditingChanged 的​​属性,它在编辑开始和编辑完成时调用。

    TextField("Enter song title", text: self.$userData.songs[self.songIndex].name, onEditingChanged: { (changed) in
        if changed {
            print("text edit has begun")
        } else {
            print("committed the change")
            saveSongs(self.userData.songs)
        }
    })
        .textFieldStyle(RoundedBorderTextFieldStyle())
        .font(.largeTitle)
    

    【讨论】:

    • onEditingChanged 不会在文本更改时调用,而是在编辑开始或停止时调用。当您在文本字段内点击时调用一次,然后在您点击完成时调用 next。它确实是这样工作的。如果您正在寻找每次击键时调用的东西,这不是它。但就其本身而言,这是一个非常好的解决方案。
    • 这个问题具体是关于文本何时更改,而不是编辑开始/停止时。这个问题的答案不正确。
    • 哦,你是对的。有用,但不是他们要的。
    【解决方案2】:

    虽然其他答案可能有效,但这个答案对我有用,我需要聆听文本更改并对其做出反应。

    第一步创建一个扩展函数

    extension Binding {
        func onChange(_ handler: @escaping (Value) -> Void) -> Binding<Value> {
            Binding(
                get: { self.wrappedValue },
                set: { newValue in
                    self.wrappedValue = newValue
                    handler(newValue)
                }
            )
        }
    }
    

    现在对 TextField 中的绑定调用 change,如下所示。

      TextField("hint", text: $text.onChange({ (value) in
          //do something here
      }))
    

    来源:HackingWithSwift

    【讨论】:

      【解决方案3】:

      SwiftUI 2.0

      从 iOS 14、macOS 11 或任何其他包含 SwiftUI 2.0 的操作系统开始,有一个名为 .onChange 的新修饰符可以检测给定 state 的任何更改:

      struct ContentView: View {
          @State var location: String = ""
      
          var body: some View {
              TextField("Your Location", text: $location)
                  .onChange(of: location) {
                      print($0) // You can do anything due to the change here.
                      // self.autocomplete($0) // like this
                  }
          }
      }
      

      SwiftUI 1.0

      对于较旧的 iOS 和其他 SwiftUI 1.0 平台,您可以使用onReceive

      .onReceive(location.publisher) { 
          print($0)
      }
      

      请注意它返回更改而不是整个值。如果您需要与onChange 相同的行为,您可以使用 combine 并按照@pawello2222 提供的答案进行操作。

      【讨论】:

        【解决方案4】:

        SwiftUI 1 & 2

        使用onReceive:

        import Combine
        import SwiftUI
        
        struct ContentView: View {
            @State var location: String = ""
        
            var body: some View {
                TextField("Search Location", text: $location)
                    .onReceive(Just(location)) { location in
                        // print(location)
                    }
            }
        }
        

        【讨论】:

        • 这实际上是一种非常好的、干净和聪明的方法!
        • 这真的有效吗?您不需要将 Just(location) 存储在某个地方以订阅它并接收它的值吗?
        • @Xaxxus 它工作得很好。我鼓励您自己尝试一下。
        • @pawello2222 我最终需要为我的用例做一些不同的事情。我有一个文本字段,其下方有一个错误标签。错误标签只需要显示是否有错误。并且触发该错误标签的验证需要在击键(300 毫秒)去抖动时运行。所以我最终做的是为我的文本字段制作一个可观察的对象视图模型。使用 text 和 errorText 发布属性而我 .onRecieve(viewModel.$text.debounce(300, runloop.main)) 使用 .debounce 触发我的验证的 $text 变量就像一个魅力。
        【解决方案5】:

        如果您需要使用ViewModel,另一种解决方案可能是:

        import SwiftUI
        import Combine
        
        class ViewModel: ObservableObject {
            @Published var location = "" {
                didSet {
                    print("set")
                    //do whatever you want
                }
            }
        }
        
        struct ContentView: View {
            @ObservedObject var viewModel = ViewModel()
        
            var body: some View {
                TextField("Search Location", text: $viewModel.location)
            }
        }
        

        【讨论】:

        • 嗨@superpuccio,如果你要使用ViewModel,你可以把代码放在didSet { ... }中。不需要:可取消/下沉。 ;-)
        • 这是一个比公认的 IMO 更清晰的答案。如果你的 body var 中有多个绑定,它可能会很快变得混乱。
        • 它需要是一个可观察的对象吗?
        • @Zorayr 是的,否则您将无法获得已发布属性的更新。
        • @FrankCheng 重绘视图时(即再次请求视图主体时)不会重新创建观察到的对象。当您的视图位于另一个视图的主体内并且该视图被重绘时,它们将被重新创建(即再次请求其主体,在该主体中,您的子视图包含观察到的对象,因此您的视图完全重新创建了它的所有属性,包括您观察到的目的)。如果是这种情况,您应该从外部将观察到的对象注入到您的视图中,或者,如果您可以使用 iOS 14,请尝试使用新的 StateObject,这正是为此目的。
        【解决方案6】:

        您可以使用自定义闭包创建绑定,如下所示:

        struct ContentView: View {
            @State var location: String = ""
        
            var body: some View {
                let binding = Binding<String>(get: {
                    self.location
                }, set: {
                    self.location = $0
                    // do whatever you want here
                })
        
                return VStack {
                    Text("Current location: \(location)")
                    TextField("Search Location", text: binding)
                }
        
            }
        }
        

        【讨论】:

        • 嗨康蒂基!只是关于您的有趣答案的问题(我从未想过以这种方式使用Binding):我想知道为什么如果我在location var 上附加一个didSet,didSet 根本不会被调用。像@State var location: String = "" { didSet { //do something }} 这样的东西我不明白为什么这个函数没有被调用。谢谢。
        • didSet on location 永远不会触发,因为您通过绑定更改的是 location.wrappedValue,而不是 location。
        • 是的,这完全有道理。我很困惑。像往常一样:谢谢你:)
        • 从 SwiftUI 中的 textFieldDidChange() 调整的绝佳解决方案,谢谢!
        • 我得到 函数声明了一个不透明的返回类型,但它的主体中没有返回语句来推断底层类型
        猜你喜欢
        • 2022-06-18
        • 1970-01-01
        • 1970-01-01
        • 2020-03-21
        • 2020-02-08
        • 1970-01-01
        • 2021-10-15
        • 1970-01-01
        • 2020-02-24
        相关资源
        最近更新 更多