Capturing type of gesture in UIGestureRecognizer extension init

I've been looking into UIGestureRecognizer trying to allow something like this:

let tap = UITapGestureRecognizer { (tapGest) in 
// Code that will be run when the gesture fires
}

Just a quick way of creating a gesture and its target during init.

I can get everything working with:

extension UIGestureRecognizer {
  convenience init(_ action: @escaping (UIGestureRecognizer) -> ()) {
       self.init()
       action(self)
  }
}

but this requires me to call

let tap = UITapGestureRecognizer { (tapGest: UITapGestureRecognizer) in

I've tried

convenience init<T>(_ action: @escaping (T) -> ()) {

but i get ( _ ) unless I :UITapGestureRecognizer
It's not a big deal to just tell the compiler the type is UITapGesture.. but since I already decalred

let tap = UITapGestureRecognizer( I thought maybe the constructor would know the proper type.

I'm curious as to why the compiler doesn't know the type in the

convenience init<T>(_ action: @escaping (T) -> ()) {

case and if theres a way to get this to work.

let tap = UITapGestureRecognizer { (tapGest) in 
// Code that will be run when the gesture fires
}

Note: I already got some code working that can take the closure and store it as the gestures target so I'm not curious about that aspect.

Thanks!

1 Like

What you really want to say is something like

convenience init(_ action: @escaping (Self) -> ()) { ... }

but that's not allowed because you can only use Self in a protocol. So use a protocol to define the init method, and then conform all of the gesture recognizer classes to the protocol:

import UIKit
import ObjectiveC

protocol GestureRecognizerActionBlockSupport where Self: UIGestureRecognizer { }

extension GestureRecognizerActionBlockSupport {
    init(_ actionBlock: @escaping (Self) -> ()) {
        self.init(target: nil, action: nil)
        self.actionBlock = { [weak self] in
            if let me = self {
                actionBlock(me)
            }
        }
        addTarget(self, action: #selector(callActionBlock))
    }
}

fileprivate extension UIGestureRecognizer {
    @IBAction func callActionBlock() {
        actionBlock?()
    }

    var actionBlock: (() -> ())? {
        get {
            return objc_getAssociatedObject(self, &actionBlockKey) as? (() -> ())
        }
        set {
        objc_setAssociatedObject(self, &actionBlockKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
}

private var actionBlockKey = 0

extension UITapGestureRecognizer: GestureRecognizerActionBlockSupport { }
extension UIPanGestureRecognizer: GestureRecognizerActionBlockSupport { }
extension UILongPressGestureRecognizer: GestureRecognizerActionBlockSupport { }
extension UIPinchGestureRecognizer: GestureRecognizerActionBlockSupport { }
extension UIRotationGestureRecognizer: GestureRecognizerActionBlockSupport { }
extension UISwipeGestureRecognizer: GestureRecognizerActionBlockSupport { }
2 Likes

Ya! That did it. Any chance you could explain a bit more whats going on so I fully understand.

Which parts are you having trouble understanding?

So I guess first off I'm curious as to why th eneed to import ObjectiveC is this just to finish up the whole example with the best way to approach adding the actionBlock?

  1. Also where does the restriction to access except in a protocol come from? Is this a design choice in swift?

Otherwise I think it makes sense. Really cool stuff.

I imported ObjectiveC to use objc_getAssociatedObject and objc_setAssociatedObject so that I could provide a complete, working implementation.

As for the restriction (on use of Self, I assume), I assume it's not allowed because it has lots of weird cases the compiler would have to handle. Suppose you write this:

class Base {
    var callback: ((Self) -> ())? = nil
    var otherBase: Base?

    func doSomethingWeird() {
        if let callback = callback, let otherBase = otherBase {
            callback(otherBase)
        }
    }
}

class Derived {
    // inherits Base.callback
}

What should Self mean here? Probably you want callback to be a (Base) -> () on an instance of Base, and a (Derived) -> () on an instance of Callback. But then the call to callback in doSomethingWeird function is invalid if you call doSomethingWeird on a Derived. There are probably lots of subtleties like this that would need to be specified and then correctly implemented in the compiler.

1 Like

Ah okay, I think I get that. One more question. Why did each gesture individually need to confrom to the Protocol? Why couldnt just UIGestureRecognizer conform?

Well, I thought if I conformed UIGestureRecognizer to the protocol, then the init would always have this signature, even in subclasses:

init(_ actionBlock: @escaping (UIGestureRecognizer) -> ())

That is, I didn't think init would take a closure that knew about the specific subclass you were initializing.

However, now I've tested it and found out I was wrong. You can just conform UIGestureRecognizer to the protocol and not conform the individual subclasses. The init will be specialized for each subclass.

1 Like

Thanks so much. Great answer