문제 상황
처음에는 UITextField().publisher(for:) 같은 방식으로 Combine과 연결하려 했지만, 실제로는 값이 한 번만 방출되고 실시간으로 텍스트 변화가 반영되지 않음
이를 해결하기 위해 NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification)와 같은 방법을 쓸 수도 있지만, 데이터 흐름이 분산되어 추적하기 어렵다고 판단
결국, UIControl 자체를 Combine 퍼블리셔로 래핑하는 방법을 선택
참고 링크의 코드를 참고해 사용했지만, 실 사용시 아래와 같이 UIControl 타입을 UIButton, UITextField로 타입 캐스팅해줘야 하는 불편함이 존재
nicknameTextField.publisher(.editingChanged)
.compactMap { $0 as? UITextField }
.map(\.text)
.sink { [weak self] in
self?.viewModel.input.nickname = $0
}
.store(in: &cancellables)
UIControl + Combine
UIButton, UITextField, UISwitch 등 UIControl은 이벤트를 감지할 수 있으니, 이를 Combine Publisher로 변환
UIControl이 AnyUIControl을 채택하게 해 UIControl에서 publisher(_:) 메서드를 사용할 수 있도록 확장
import UIKit
import Combine
protocol AnyUIControl {}
extension AnyUIControl where Self: UIControl {
func publisher(_ event: Self.Event) -> UIControl.EventPublisher<Self> {
return EventPublisher(control: self, event: event)
}
}
extension UIControl: AnyUIControl {}
EventPublisher
extension UIControl {
struct EventPublisher<Output: UIControl>: Publisher {
typealias Failure = Never
private let control: Output
private let event: UIControl.Event
init(control: Output, event: UIControl.Event) {
self.control = control
self.event = event
}
func receive<S>(subscriber: S)
where S: Subscriber, S.Input == Output, S.Failure == Never {
let subscription = EventSubscription(control: control, subscrier: subscriber, event: event)
subscriber.receive(subscription: subscription)
}
}
}
Subscription
private final class EventSubscription<
EventSubscriber: Subscriber,
Input: UIControl
>: Subscription where EventSubscriber.Input == Input, EventSubscriber.Failure == Never {
private let control: Input
private var subscriber: EventSubscriber?
init(control: Input, subscrier: EventSubscriber, event: UIControl.Event) {
self.control = control
self.subscriber = subscrier
control.addTarget(self, action: #selector(handler), for: event)
}
func request(_ demand: Subscribers.Demand) { }
func cancel() {
subscriber = nil
}
@objc private func handler() {
_ = subscriber?.receive(control)
}
}
사용 예시
final class ViewController: UIViewController {
private var cancellables = Set<AnyCancellable>()
private let textField = UITextField()
private let button = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
textField.publisher(.editingChanged)
.sink { tf in
print("텍스트 변경됨:", tf.text ?? "")
}
.store(in: &cancellables)
button.publisher(.touchUpInside)
.sink { _ in
print("버튼 클릭됨")
}
.store(in: &cancellables)
}
}
참고
'Swift > UIKit' 카테고리의 다른 글
| UIKit: DataSource, DiffableDataSource (1) | 2025.08.30 |
|---|---|
| UIKit: UITextView (1) | 2025.08.30 |
| 버튼을 통해 IndexPath 전달하기 (2) | 2025.08.18 |
| UIKit: TableView, CollectionView register, dequeue 셀 식별자 (2) | 2025.07.31 |
| UIKit: 테이블 뷰 셀 내부에 원형 뷰 만드는 방법 (3) | 2025.07.28 |