3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

iOSAdvent Calendar 2020

Day 13

CombineでUIViewのタップ等のジェスチャーを取得する

Last updated at Posted at 2020-12-13

CombineにおいてUIViewのタップ等のジェスチャーを取得する方法です。

参考にした記事は
https://jllnmercier.medium.com/combine-handling-uikits-gestures-with-a-publisher-c9374de5a478
にあります。

以下のコードはほぼその記事からの抜粋です。

まず、Publisherを実装します、後ほど定義するGestureSubscriptionを使うところがポイントです。

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で定義します。

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
        }
    }
}

GestureSubscriptionにおいて従来ながらのUIKitのaddTargetを利用しSubscriptionを実装します。

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
    }

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

最後にこれをUIViewの拡張メソッドとしてもたせます。

extension UIView {
    func gesturePublisher(_ gestureType: GestureType = .tap()) ->
        GesturePublisher
    {
        .init(view: self, gestureType: gestureType)
    }
}

これで簡単にジェスチャー扱えます。

       var cancellables = Set<AnyCancellable>()
       view.gesture().sink { recognizer in
           print("Tapped !")
       }.store(in: &cancellables)
       // prints "Tapped !"

       view.gesture(.swipe()).sink { recognizer in
           print("Swiped !")
       }.store(in: &cancellables)
       // prints "Swiped !"

以上となります。ありがとうございました。

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?