반응형

iOS 13 이상 부터 Combine을 사용할수 있기에 Rx를 사용하지 않더라도 다음과 같은 Extension으로 UIKit Control들의 이벤트를 처리 할 수 있습니다.

import UIKit
import Combine

extension UIControl {
    /// Control Publisher
    func controlPublisher(for event: UIControl.Event) -> UIControl.EventPublisher {
        return UIControl.EventPublisher(control: self, event: event)
      }
    
    /// Event Publisher
    struct EventPublisher: Publisher {
        typealias Output = UIControl
        typealias Failure = Never
        
        let control: UIControl
        let event: UIControl.Event
        
        func receive<T>(subscriber: T) where T: Subscriber, Never == T.Failure, UIControl == T.Input {
            let subscription = EventSubscription(control: control, subscrier: subscriber, event: event)
            subscriber.receive(subscription: subscription)
        }
    }
    
    /// Event Subscription
    private class EventSubscription<EventSubscriber: Subscriber>: Subscription where EventSubscriber.Input == UIControl, EventSubscriber.Failure == Never {
        let control: UIControl
        let event: UIControl.Event
        var subscriber: EventSubscriber?
        
        init(control: UIControl, subscrier: EventSubscriber, event: UIControl.Event) {
            self.control = control
            self.subscriber = subscrier
            self.event = event
            
            control.addTarget(self, action: #selector(eventDidOccur), for: event)
        }
        
        func request(_ demand: Subscribers.Demand) {}
        
        func cancel() {
            subscriber = nil
            control.removeTarget(self, action: #selector(eventDidOccur), for: event)
        }
        
        @objc func eventDidOccur() {
            _ = subscriber?.receive(control)
        }
    }
}

extension UITextField {
    /// 텍스트 필드 입력
    var textPublisher: AnyPublisher<String?, Never> {
        controlPublisher(for: .editingChanged)
            .compactMap { $0 as? UITextField }
            .map { $0.text }
            .eraseToAnyPublisher()
    }
    
    /// 텍스트 필드 입력 Debounce
    var textDebouncePublisher: AnyPublisher<String?, Never> {
        textPublisher.debounce(for: 0.1, scheduler: RunLoop.main)
            .eraseToAnyPublisher()
    }
    
    /// 텍스트 필드 편집 종료  Publisher
    var didEndEditingPublisher: AnyPublisher<Void, Never> {
        controlPublisher(for: .editingDidEnd)
            .map { _ in }
            .eraseToAnyPublisher()
    }
}

extension UIButton {
    /// 버튼 touchUpInside 처리
    var tapPublisher: AnyPublisher<Void, Never> {
        controlPublisher(for: .touchUpInside)
            .map { _ in }
            .eraseToAnyPublisher()
    }
    
    /// 버튼 touchUpInside Debounce
    var tapDebouncePublisher: AnyPublisher<Void, Never> {
        tapPublisher.debounce(for: 0.1, scheduler: RunLoop.main)
            .eraseToAnyPublisher()
    }
}

extension UISwitch {
    /// valueChanged 처리
    var statePublisher: AnyPublisher<Bool, Never> {
        controlPublisher(for: .valueChanged)
            .compactMap { $0 as? UISwitch }
            .map { $0.isOn }
            .eraseToAnyPublisher()
    }
}

extension UIStepper {
    /// valueChanged 처리
    var valuePublisher: AnyPublisher<Double, Never> {
        controlPublisher(for: .valueChanged)
            .compactMap { $0 as? UIStepper }
            .map { $0.value }
            .eraseToAnyPublisher()
    }
}

extension UISegmentedControl {
    /// valueChanged 처리
    var selectionPublisher: AnyPublisher<Int, Never> {
        controlPublisher(for: .valueChanged)
            .compactMap { $0 as? UISegmentedControl }
            .map { $0.selectedSegmentIndex }
            .eraseToAnyPublisher()
    }
}

extension UISlider {
    /// valueChanged 처리
    var valuePublisher: AnyPublisher<Float, Never> {
        controlPublisher(for: .valueChanged)
            .compactMap { $0 as? UISlider }
            .map { $0.value }
            .eraseToAnyPublisher()
    }
}

// UIView Gesture 처리 
// 참고: https://jllnmercier.medium.com/combine-handling-uikits-gestures-with-a-publisher-c9374de5a478
extension UIView {
    func gesture(_ gestureType: GestureType = .tap()) -> GesturePublisher {
        .init(view: self, gestureType: gestureType)
    }
    
    struct GesturePublisher: Publisher {
        typealias Output = GestureType
        typealias Failure = Never
        private let view: UIView
        private let gestureType: GestureType
        init(view: UIView, gestureType: GestureType) {
            self.view = view
            self.gestureType = gestureType
        }
        func receive<S>(subscriber: S) where S: Subscriber,
                                             GesturePublisher.Failure == S.Failure, GesturePublisher.Output
        == S.Input {
            let subscription = GestureSubscription(
                subscriber: subscriber,
                view: view,
                gestureType: gestureType
            )
            subscriber.receive(subscription: subscription)
        }
    }
    enum GestureType {
        case tap(UITapGestureRecognizer = .init())
        case swipe(UISwipeGestureRecognizer = .init())
        case longPress(UILongPressGestureRecognizer = .init())
        case pan(UIPanGestureRecognizer = .init())
        case pinch(UIPinchGestureRecognizer = .init())
        case edge(UIScreenEdgePanGestureRecognizer = .init())
        func get() -> UIGestureRecognizer {
            switch self {
            case let .tap(tapGesture):
                return tapGesture
            case let .swipe(swipeGesture):
                return swipeGesture
            case let .longPress(longPressGesture):
                return longPressGesture
            case let .pan(panGesture):
                return panGesture
            case let .pinch(pinchGesture):
                return pinchGesture
            case let .edge(edgePanGesture):
                return edgePanGesture
            }
        }
    }
    
    class GestureSubscription<S: Subscriber>: Subscription where S.Input == GestureType, S.Failure == Never {
        private var subscriber: S?
        private var gestureType: GestureType
        private var view: UIView
        init(subscriber: S, view: UIView, gestureType: GestureType) {
            self.subscriber = subscriber
            self.view = view
            self.gestureType = gestureType
            configureGesture(gestureType)
        }
        private func configureGesture(_ gestureType: GestureType) {
            let gesture = gestureType.get()
            gesture.addTarget(self, action: #selector(handler))
            view.addGestureRecognizer(gesture)
        }
        func request(_ demand: Subscribers.Demand) { }
        func cancel() {
            subscriber = nil
            view.removeGestureRecognizer(gestureType.get())
        }
        @objc
        private func handler() {
            _ = subscriber?.receive(gestureType)
        }
    }
}

 

반응형
Posted by 까칠코더
,