UIKit의 UIControl을 Combine으로 연결하는 방법

Ios_Roy·2025년 8월 6일
0

TIL

목록 보기
17/25
post-thumbnail

📲 UIKit의 UIControl을 Combine으로 연결하는 방법

UIKit 기반 앱에서도 Combine을 활용한 선언적 이벤트 처리가 가능합니다.
이 문서에서는 UIButton, UISwitch, UITextFieldUIControl 기반 컴포넌트의 이벤트를
Combine Publisher로 래핑하여 사용하는 방법을 소개합니다.


💡 목표

아래와 같이 UIKit 컨트롤의 이벤트를 Combine으로 선언적으로 처리하는 게 목표입니다.

button
  .publisher(for: .touchUpInside)
  .sink {
    print("버튼이 눌렸습니다!")
  }
  .store(in: &cancellables)

UIKit은 Combine을 기본적으로 지원하지 않기 때문에, 커스텀 Publisher를 직접 구현해야 합니다.

🛠 전체 구현 코드 (UIControl + Combine)

import Combine
import UIKit

extension UIControl {
func publisher(for event: UIControl.Event) -> UIControl.EventPublisher {
  return UIControl.EventPublisher(control: self, event: event)
}

struct EventPublisher: Publisher {
  typealias Output = Void
  typealias Failure = Never

  let control: UIControl
  let event: UIControl.Event

  func receive<S>(subscriber: S) where S: Subscriber, Failure == S.Failure, Output == S.Input {
    let subscription = EventSubscription(subscriber: subscriber, control: control, event: event)
    subscriber.receive(subscription: subscription)
  }

  private final class EventSubscription<S: Subscriber>: Subscription where S.Input == Void {
    private var subscriber: S?
    weak private var control: UIControl?

    init(subscriber: S, control: UIControl, event: UIControl.Event) {
      self.subscriber = subscriber
      self.control = control
      control.addTarget(self, action: #selector(eventHandler), for: event)
    }

    func request(_ demand: Subscribers.Demand) {
      // 수요 관리 없음 (Void 이벤트 처리이므로)
    }

    func cancel() {
      subscriber = nil
    }

    @objc private func eventHandler() {
      _ = subscriber?.receive(())
    }
  }
}
}

✅ 사용 예제

import UIKit
import Combine

final class ExampleViewController: UIViewController {
private var cancellables = Set<AnyCancellable>()
private let button = UIButton(type: .system)

override func viewDidLoad() {
 super.viewDidLoad()
 view.backgroundColor = .white

 button.setTitle("눌러보세요", for: .normal)
 button.titleLabel?.font = .systemFont(ofSize: 18)
 button.backgroundColor = .systemBlue
 button.setTitleColor(.white, for: .normal)
 button.layer.cornerRadius = 8
 button.frame = CGRect(x: 100, y: 200, width: 160, height: 50)
 view.addSubview(button)

 button
   .publisher(for: .touchUpInside)
   .sink {
     print("✅ 버튼이 눌렸습니다")
   }
   .store(in: &cancellables)
}
}

🔍 작동 방식 요약

구성 요소설명
publisher(for:)UIControl의 이벤트를 Combine Publisher로 래핑
EventPublisherCombine의 Publisher 프로토콜을 구현한 커스텀 퍼블리셔
EventSubscription실제 UI 이벤트 발생 시 subscriber에게 이벤트를 전달
@objc eventHandler()UIControl 이벤트가 발생하면 호출되는 메서드

📌 Combine 핵심 개념 복습

용어설명
Publisher데이터를 발행하는 주체
예: Just, URLSession, 배열 등
Subscriber데이터를 수신하는 주체
예: sink, assign
Operator데이터를 중간에 변형/필터링하는 도구
예: map, filter, debounce

💡 Combine의 기본 흐름:
Publisher → Operator → Subscriber

✅ Subject와의 차이점

항목설명
커스텀 PublisherUIKit 이벤트에 특화됨
send() 호출 없이 이벤트 감지 가능
PassthroughSubject외부에서 수동으로 .send() 호출 필요
CurrentValueSubject항상 최신 상태를 유지하며
구독 시 즉시 값 전달 가능

🎯 활용 가능한 UIControl 이벤트

컨트롤사용 가능한 이벤트 예시
UIButton.touchUpInside, .touchDown
UISwitch.valueChanged
UITextField.editingChanged, .editingDidEnd
UISlider.valueChanged, .touchUpInside

🧩 응용 아이디어

  • UITextField와 Combine을 이용한 양방향 바인딩 구현
  • UISwitch 상태 변화 감지하여 뷰 상태 동기화 및 UI 업데이트
  • Combine 기반 MVVM 아키텍처에 자연스럽게 통합 가능

✅ 마무리 요약

항목설명
Combine 도입 이유선언적 UI 이벤트 처리
커스텀 Publisher 구현 이유UIKit ↔ Combine 연결
핵심 클래스Publisher / Subscription / UIControl
활용 가능 이벤트UIButton, UISwitch, UITextField
사용 예시.publisher(for: .touchUpInside).sink {} 처리

UIKit에서도 Combine을 통해 구조적이고 선언적인 방식으로 UI 이벤트를 처리할 수 있습니다.
위 확장을 프로젝트에 도입하면, MVVM + Combine 구조에 매우 유용하게 활용할 수 있습니다.

profile
iOS 개발자 공부하는 Roy

0개의 댓글