Swift Combine — как получить издателя, который доставляет события для каждого изменения символа текстового свойства UITextField

Я заметил, что

textField.publisher(for: \.text)

доставляет события по завершении редактирования, но не для каждого символа/изменения редактирования. Как мне получить издателя, который отправляет четы для каждого изменения? В ReactiveSwift это будет

textField.reactive.continousTextValues()

А в RxSwift это было бы просто (Как вы получаете сигнал каждый раз, когда свойство текста UITextField изменяется в RxSwift)

textField.rx.text

Подходы, которые я использовал:

  • проверка методом publisher(for:options:), но подходящих вариантов для желаемого результата нет.
  • добавление цели/действия textField.addTarget(self, action: #selector(theTextFieldDidChange), for: .editingChanged) (событие изменения текста UITextField)
  • делая по существу то же самое, что и предыдущий шаг, подключая действие через построитель интерфейса, что приводит к дополнительной работе и загромождению кода.
  • Смотрю видеоролики WWDC 2019 года о Combine. Они не имели дело с текстовыми полями, а вместо этого использовали переменные @Published, скрывая, откуда на самом деле пришли значения - (или я что-то пропустил?).

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


person thetrutz    schedule 11.03.2020    source источник
comment
Почему бы вам не использовать TextField вместо UITextField? Это обновляет привязку text при каждом изменении символа.   -  person Dávid Pásztor    schedule 11.03.2020
comment
Есть большая вероятность, что ОП не (хочет) использовать SwiftUI   -  person donnywals    schedule 12.03.2020


Ответы (4)


К сожалению, Apple не добавила издателей для этого в UIKit, и поскольку UIControl не реализует KVO, publisher(for:) издатель не работает так, как ожидалось. Если вы не против добавить зависимость в свой проект, есть несколько хороших издателей UIControl здесь.

В качестве альтернативы вы можете реализовать свой собственный издатель, который делает это, или подписаться на изменения текстового поля по-старому.

person donnywals    schedule 12.03.2020

Вы всегда можете создать собственный издатель для своих нужд. Например, здесь я создал издатель TextField, который оборачивает действие textFieldDidChange для textField и отправляет строку после каждого введенного/удаленного символа! Пожалуйста, скопируйте ссылку, SO ее не парсит:

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

person Dmitriy Lupych    schedule 11.03.2020

В случае 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()

person kprater    schedule 10.02.2021

Если это то, что вы ищете, я думаю, 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)
    }
}

Дайте мне знать, если это было вашей реальной целью.

person denis_lor    schedule 10.09.2020