如何在 SwiftUI 中检测 TextField 的实时变化?

Posted

技术标签:

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

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

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

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

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

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

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

【问题讨论】:

【参考方案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() 调整的绝佳解决方案,谢谢! 我得到 函数声明了一个不透明的返回类型,但它的主体中没有返回语句来推断底层类型

以上是关于如何在 SwiftUI 中检测 TextField 的实时变化?的主要内容,如果未能解决你的问题,请参考以下文章

检测用户何时停止/暂停在 TextField 中写入 - SwiftUI

如何防止 TextField 在 SwiftUI 列表中消失?

如何检测 SwiftUI 中的 @Published 值发生了啥变化?

我如何在SWIFTUI中把textField作为firstResponder,就像Swift一样:textField.becomesFirstResponder。

如何在 SwiftUI TextField 组件中使用 textChange 事件?

SwiftUI - 如何在另一个视图中响应 TextField onCommit?