Swift Combine - 如何获得一个发布者,为 UITextField 的文本属性的每个字符更改提供事件

Posted

技术标签:

【中文标题】Swift Combine - 如何获得一个发布者,为 UITextField 的文本属性的每个字符更改提供事件【英文标题】:Swift Combine - How to get a Publisher that delivers events for every character change of UITextField's text property 【发布时间】:2020-03-11 16:05:45 【问题描述】:

我注意到了

textField.publisher(for: \.text)

在编辑完成时提供事件,但不是针对每个字符/编辑更改。我如何获得发布者,为每次更改发送偶数? 在 ReactiveSwift 中它会是

textField.reactive.continousTextValues()

在 RxSwift 中它只是 (How do you get a signal every time a UITextField text property changes in RxSwift)

textField.rx.text

我采取的方法:

正在检查publisher(for:options:) 方法,但没有合适的选项来获得所需的结果。 添加目标/操作textField.addTarget(self, action: #selector(theTextFieldDidChange), for: .editingChanged) (UITextField text change event) 通过界面生成器连接动作,与上一步基本相同,这都会导致额外的工作和混乱的代码。 观看有关 Combine 的 2019 年 WWDC 视频。他们不是在处理文本字段,而是使用@Published 变量,隐藏值实际上来自 - (或者我错过了什么?)。

目前我不知道如何做到这一点,我觉得有回到 ReactiveSwift 的倾向,但我想在退后一步之前问你。

【问题讨论】:

为什么不用TextField 而不是UITextField?这会在每个字符更改时更新其 text 绑定。 OP 很有可能不(想要)使用 SwiftUI 【参考方案1】:

如果是 UITextField,您可以使用此扩展:

  extension UITextField 
    func textPublisher() -> AnyPublisher<String, Never> 
        NotificationCenter.default
            .publisher(for: UITextField.textDidChangeNotification, object: self)
            .map  ($0.object as? UITextField)?.text  ?? "" 
            .eraseToAnyPublisher()
    
  

用作:textField.textPublisher()

【讨论】:

【参考方案2】:

不幸的是,Apple 没有为此向 UIKit 添加发布者,并且由于 UIControl 没有实现 KVO,publisher(for:) 发布者无法按预期工作。如果您可以在项目中添加依赖项,那么有几个不错的 UIControl 发布者here。

或者,您可以实现自己的发布者来执行此操作,或者订阅 textfield 更改旧的学校方式。

【讨论】:

【参考方案3】:

您始终可以根据需要创建自定义发布者。例如,在这里我创建了 TextField 发布者,它为 textField 包装了 textFieldDidChange 操作,并在输入/删除每个字符后发送字符串!请复制链接,所以不会解析它:

https://github.com/DmitryLupich/Combine-UIKit/blob/master/CombineCustomPublishers/?%20Publishers/TextFieldPubisher.swift

【讨论】:

【参考方案4】:

如果这是您要查找的内容,我猜UITextField 会同时发出通知和控制事件,因此您可以通过例如收听通知来实现:

import UIKit
import Combine

class ViewController: UIViewController 

    @IBOutlet private var textField: UITextField!

    var cancellables = Set<AnyCancellable>()

    override func viewDidLoad() 
        super.viewDidLoad()

        let textFieldPublisher = NotificationCenter.default
            .publisher(for: UITextField.textDidChangeNotification, object: textField)
            .map( 
                ($0.object as? UITextField)?.text
            )
        
        textFieldPublisher
            .receive(on: RunLoop.main)
            .sink(receiveValue:  [weak self] value in
                print("UITextField.text changed to: \(value)")
            )
            .store(in: &cancellables)
    

让我知道这是否是你的实际目标。

【讨论】:

以上是关于Swift Combine - 如何获得一个发布者,为 UITextField 的文本属性的每个字符更改提供事件的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Combine + Swift 复制 PromiseKit 风格的链式异步流

如何通过另一个可观察对象观察已发布的属性 - Swift Combine

如何在 Swift 中使用 Combine 读取 JSON 错误对象的属性值?

Swift Combine:如何从发布者列表中创建单个发布者?

Swift Combine:如何将 `AnyPublisher<[Foo], *>` 转换为 `AnyPublisher<Foo, *>`?

Combine如何驯服Future发布器的“怪癖”