Skip to content

Instantly share code, notes, and snippets.

@loganwright
Last active August 4, 2023 03:50
UIView Gesture Recognizer Extension For Swift

Interface for dealing with gesture recognizers via native swift closure syntax

import UIKit

class ViewController: UIViewController {
                            
    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSingleTapGestureRecognizerWithResponder { (tap) -> Void in
            println("Tapped: \(tap)")
        }
        view.addSingleTouchPanGestureRecognizerWithResponder { (pan) -> Void in
            println("Panned: \(pan)")
        }
        
        view.addLongPressGestureRecognizerWithResponder(handleLongPress)
    }

    func handleLongPress(longPress: UILongPressGestureRecognizer) {
        println("LongPressed: \(longPress)")
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}
extension UIView {
typealias TapResponseClosure = (tap: UITapGestureRecognizer) -> Void
typealias PanResponseClosure = (pan: UIPanGestureRecognizer) -> Void
typealias SwipeResponseClosure = (swipe: UISwipeGestureRecognizer) -> Void
typealias PinchResponseClosure = (pinch: UIPinchGestureRecognizer) -> Void
typealias LongPressResponseClosure = (longPress: UILongPressGestureRecognizer) -> Void
typealias RotationResponseClosure = (rotation: UIRotationGestureRecognizer) -> Void
private struct ClosureStorage {
static var TapClosureStorage: [UITapGestureRecognizer : TapResponseClosure] = [:]
static var PanClosureStorage: [UIPanGestureRecognizer : PanResponseClosure] = [:]
static var SwipeClosureStorage: [UISwipeGestureRecognizer : SwipeResponseClosure] = [:]
static var PinchClosureStorage: [UIPinchGestureRecognizer : PinchResponseClosure] = [:]
static var LongPressClosureStorage: [UILongPressGestureRecognizer: LongPressResponseClosure] = [:]
static var RotationClosureStorage: [UIRotationGestureRecognizer: RotationResponseClosure] = [:]
}
private struct Swizzler {
private static var OnceToken : dispatch_once_t = 0
static func Swizzle() {
dispatch_once(&OnceToken) {
let UIViewClass: AnyClass! = NSClassFromString("UIView")
let originalSelector = Selector("removeFromSuperview")
let swizzleSelector = Selector("swizzled_removeFromSuperview")
let original: Method = class_getInstanceMethod(UIViewClass, originalSelector)
let swizzle: Method = class_getInstanceMethod(UIViewClass, swizzleSelector)
method_exchangeImplementations(original, swizzle)
}
}
}
func swizzled_removeFromSuperview() {
self.removeGestureRecognizersFromStorage()
/*
Will call the original representation of removeFromSuperview, not endless cycle:
http://darkdust.net/writings/objective-c/method-swizzling
*/
self.swizzled_removeFromSuperview()
}
func removeGestureRecognizersFromStorage() {
if let gestureRecognizers = self.gestureRecognizers {
for recognizer: UIGestureRecognizer in gestureRecognizers as [UIGestureRecognizer] {
if let tap = recognizer as? UITapGestureRecognizer {
ClosureStorage.TapClosureStorage[tap] = nil
}
else if let pan = recognizer as? UIPanGestureRecognizer {
ClosureStorage.PanClosureStorage[pan] = nil
}
else if let swipe = recognizer as? UISwipeGestureRecognizer {
ClosureStorage.SwipeClosureStorage[swipe] = nil
}
else if let pinch = recognizer as? UIPinchGestureRecognizer {
ClosureStorage.PinchClosureStorage[pinch] = nil
}
else if let rotation = recognizer as? UIRotationGestureRecognizer {
ClosureStorage.RotationClosureStorage[rotation] = nil
}
else if let longPress = recognizer as? UILongPressGestureRecognizer {
ClosureStorage.LongPressClosureStorage[longPress] = nil
}
}
}
}
// MARK: Taps
func addSingleTapGestureRecognizerWithResponder(responder: TapResponseClosure) {
self.addTapGestureRecognizerForNumberOfTaps(withResponder: responder)
}
func addDoubleTapGestureRecognizerWithResponder(responder: TapResponseClosure) {
self.addTapGestureRecognizerForNumberOfTaps(numberOfTaps: 2, withResponder: responder)
}
func addTapGestureRecognizerForNumberOfTaps(numberOfTaps: Int = 1, numberOfTouches: Int = 1, withResponder responder: TapResponseClosure) {
let tap = UITapGestureRecognizer()
tap.numberOfTapsRequired = numberOfTaps
tap.numberOfTouchesRequired = numberOfTouches
tap.addTarget(self, action: "handleTap:")
self.addGestureRecognizer(tap)
ClosureStorage.TapClosureStorage[tap] = responder
Swizzler.Swizzle()
}
func handleTap(sender: UITapGestureRecognizer) {
if let closureForTap = ClosureStorage.TapClosureStorage[sender] {
closureForTap(tap: sender)
}
}
// MARK: Pans
func addSingleTouchPanGestureRecognizerWithResponder(responder: PanResponseClosure) {
self.addPanGestureRecognizerForNumberOfTouches(1, withResponder: responder)
}
func addDoubleTouchPanGestureRecognizerWithResponder(responder: PanResponseClosure) {
self.addPanGestureRecognizerForNumberOfTouches(2, withResponder: responder)
}
func addPanGestureRecognizerForNumberOfTouches(numberOfTouches: Int, withResponder responder: PanResponseClosure) {
let pan = UIPanGestureRecognizer()
pan.minimumNumberOfTouches = numberOfTouches
pan.addTarget(self, action: "handlePan:")
self.addGestureRecognizer(pan)
ClosureStorage.PanClosureStorage[pan] = responder
Swizzler.Swizzle()
}
func handlePan(sender: UIPanGestureRecognizer) {
if let closureForPan = ClosureStorage.PanClosureStorage[sender] {
closureForPan(pan: sender)
}
}
// MARK: Swipes
func addLeftSwipeGestureRecognizerWithResponder(responder: SwipeResponseClosure) {
self.addLeftSwipeGestureRecognizerForNumberOfTouches(1, withResponder: responder)
}
func addLeftSwipeGestureRecognizerForNumberOfTouches(numberOfTouches: Int, withResponder responder: SwipeResponseClosure) {
self.addSwipeGestureRecognizerForNumberOfTouches(numberOfTouches, forSwipeDirection: .Left, withResponder: responder)
}
func addRightSwipeGestureRecognizerWithResponder(responder: SwipeResponseClosure) {
self.addRightSwipeGestureRecognizerForNumberOfTouches(1, withResponder: responder)
}
func addRightSwipeGestureRecognizerForNumberOfTouches(numberOfTouches: Int, withResponder responder: SwipeResponseClosure) {
self.addSwipeGestureRecognizerForNumberOfTouches(numberOfTouches, forSwipeDirection: .Right, withResponder: responder)
}
func addUpSwipeGestureRecognizerWithResponder(responder: SwipeResponseClosure) {
self.addUpSwipeGestureRecognizerForNumberOfTouches(1, withResponder: responder)
}
func addUpSwipeGestureRecognizerForNumberOfTouches(numberOfTouches: Int, withResponder responder: SwipeResponseClosure) {
self.addSwipeGestureRecognizerForNumberOfTouches(numberOfTouches, forSwipeDirection: .Up, withResponder: responder)
}
func addDownSwipeGestureRecognizerWithResponder(responder: SwipeResponseClosure) {
self.addDownSwipeGestureRecognizerForNumberOfTouches(1, withResponder: responder)
}
func addDownSwipeGestureRecognizerForNumberOfTouches(numberOfTouches: Int, withResponder responder: SwipeResponseClosure) {
self.addSwipeGestureRecognizerForNumberOfTouches(numberOfTouches, forSwipeDirection: .Down, withResponder: responder)
}
func addSwipeGestureRecognizerForNumberOfTouches(numberOfTouches: Int, forSwipeDirection swipeDirection: UISwipeGestureRecognizerDirection, withResponder responder: SwipeResponseClosure) {
let swipe = UISwipeGestureRecognizer()
swipe.direction = swipeDirection
swipe.numberOfTouchesRequired = numberOfTouches
swipe.addTarget(self, action: "handleSwipe:")
self.addGestureRecognizer(swipe)
ClosureStorage.SwipeClosureStorage[swipe] = responder
Swizzler.Swizzle()
}
func handleSwipe(sender: UISwipeGestureRecognizer) {
if let closureForSwipe = ClosureStorage.SwipeClosureStorage[sender] {
closureForSwipe(swipe: sender)
}
}
// MARK: Pinches
func addPinchGestureRecognizerWithResponder(responder: PinchResponseClosure) {
let pinch = UIPinchGestureRecognizer()
pinch.addTarget(self, action: "handlePinch:")
self.addGestureRecognizer(pinch)
ClosureStorage.PinchClosureStorage[pinch] = responder
Swizzler.Swizzle()
}
func handlePinch(sender: UIPinchGestureRecognizer) {
if let closureForPinch = ClosureStorage.PinchClosureStorage[sender] {
closureForPinch(pinch: sender)
}
}
// MARK: LongPress
func addLongPressGestureRecognizerWithResponder(responder: LongPressResponseClosure) {
self.addLongPressGestureRecognizerForNumberOfTouches(1, withResponder: responder)
}
func addLongPressGestureRecognizerForNumberOfTouches(numberOfTouches: Int, withResponder responder: LongPressResponseClosure) {
let longPress = UILongPressGestureRecognizer()
longPress.numberOfTouchesRequired = numberOfTouches
longPress.addTarget(self, action: "handleLongPress:")
self.addGestureRecognizer(longPress)
ClosureStorage.LongPressClosureStorage[longPress] = responder
Swizzler.Swizzle()
}
func handleLongPress(sender: UILongPressGestureRecognizer) {
if let closureForLongPinch = ClosureStorage.LongPressClosureStorage[sender] {
closureForLongPinch(longPress: sender)
}
}
// MARK: Rotation
func addRotationGestureRecognizerWithResponder(responder: RotationResponseClosure) {
let rotation = UIRotationGestureRecognizer()
rotation.addTarget(self, action: "handleRotation:")
self.addGestureRecognizer(rotation)
ClosureStorage.RotationClosureStorage[rotation] = responder
Swizzler.Swizzle()
}
func handleRotation(sender: UIRotationGestureRecognizer) {
if let closureForRotation = ClosureStorage.RotationClosureStorage[sender] {
closureForRotation(rotation: sender)
}
}
}
@wpfilf
Copy link

wpfilf commented Nov 12, 2015

Thx for this nice snippet. Just a small but important thing:

view.addSingleTapGestureRecognizerWithResponder({ [unowned self] -> Void in
print("Tapped: (tap)")
self.myLabel.text = "myText"
})

As of Swift 2 you need to add [unowned self] in order to avoid strong references with self. causing memory leaks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment