Skip to content

Instantly share code, notes, and snippets.

@akbsteam
Forked from loganwright/Readme.md
Last active August 19, 2022 11:41
Show Gist options
  • Select an option

  • Save akbsteam/a9ae4710dd082bd960886970fcfe08bc to your computer and use it in GitHub Desktop.

Select an option

Save akbsteam/a9ae4710dd082bd960886970fcfe08bc to your computer and use it in GitHub Desktop.
UIView Gesture Recognizer Extension For Swift
//
// The MIT License (MIT)
//
// Copyright (c) 2017 Andy Bennett (@akbsteam)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
import ReactiveKit
import Bond
public extension ReactiveExtensions where Base: UIView {
public func addGesture<T: UIGestureRecognizer>(_ gesture: T) -> SafeSignal<T> {
let base = self.base
return Signal { [weak base] observer in
guard let base = base else {
observer.completed()
return NonDisposable.instance
}
let target = BNDGestureTarget(view: base, gesture: gesture) { recog in
observer.next(recog as! T)
}
return BlockDisposable {
target.unregister()
}
}.take(until: base.deallocated)
}
public func tapGesture(numberOfTaps: Int = 1, numberOfTouches: Int = 1) -> SafeSignal<UITapGestureRecognizer> {
let gesture = UITapGestureRecognizer()
gesture.numberOfTapsRequired = numberOfTaps
gesture.numberOfTouchesRequired = numberOfTouches
return self.addGesture(gesture)
}
public func panGesture(numberOfTouches: Int = 1) -> SafeSignal<UIPanGestureRecognizer> {
let gesture = UIPanGestureRecognizer()
gesture.minimumNumberOfTouches = numberOfTouches
return self.addGesture(gesture)
}
public func swipeGesture(numberOfTouches: Int, direction: UISwipeGestureRecognizerDirection) -> SafeSignal<UISwipeGestureRecognizer> {
let gesture = UISwipeGestureRecognizer()
gesture.numberOfTouchesRequired = numberOfTouches
gesture.direction = direction
return self.addGesture(gesture)
}
public func pinchGestureRecognizer() -> SafeSignal<UIPinchGestureRecognizer> {
return self.addGesture(UIPinchGestureRecognizer())
}
public func longPressGesture(numberOfTaps: Int = 0, numberOfTouches: Int = 1, minimumPressDuration: CFTimeInterval = 0.3, allowableMovement: CGFloat = 10) -> SafeSignal<UILongPressGestureRecognizer> {
let gesture = UILongPressGestureRecognizer()
gesture.numberOfTapsRequired = numberOfTaps
gesture.numberOfTouchesRequired = numberOfTouches
gesture.minimumPressDuration = minimumPressDuration
gesture.allowableMovement = allowableMovement
return self.addGesture(gesture)
}
public func rotationGesture() -> SafeSignal<UIRotationGestureRecognizer> {
return self.addGesture(UIRotationGestureRecognizer())
}
}
@objc fileprivate class BNDGestureTarget: NSObject {
private weak var view: UIView?
private let observer: (UIGestureRecognizer) -> Void
private let gesture: UIGestureRecognizer
fileprivate init(view: UIView, gesture: UIGestureRecognizer, observer: @escaping (UIGestureRecognizer) -> Void) {
self.view = view
self.gesture = gesture
self.observer = observer
super.init()
gesture.addTarget(self, action: #selector(actionHandler(recogniser:)))
view.addGestureRecognizer(gesture)
}
@objc private func actionHandler(recogniser: UIGestureRecognizer) {
observer(recogniser)
}
fileprivate func unregister() {
view?.removeGestureRecognizer(gesture)
}
deinit {
unregister()
}
}
@tonyarnold
Copy link
Copy Markdown

My first comment is that normally, the signals/subjects/bonds are added as extensions on ReactiveExtensions, like this:

public extension ReactiveExtensions where Base: UIView {

  public func tapGesture(numberOfTaps: Int = 1, numberOfTouches: Int = 1) -> SafeSignal<UITapGestureRecognizer> {
    return SafeSignal { observer in
      // Setup your signal
      return NonDisposable.instance
    }
  }
}

// Which means it's used like:

myView.reactive.tapGesture().bind(to: self) { _, _ in 
  // Handle the tap
}

I can see how the swizzling might seem like it's helping, but personally I'd avoid that at all costs. What I'd suggest is looking at how Bond's UIControl BNDControlTarget handles the same kind of activity. It's much lighter, and there's no need to swizzle UIKit-provided classes.

@akbsteam
Copy link
Copy Markdown
Author

@tonyarnold revision 6 is rewritten in line with your comments; it compiles and seems to work ... comments?

@tonyarnold
Copy link
Copy Markdown

@akbsteam looks good, and much more in line with what's in Bond now! My final comment (if you're intending to submit this to the project) would be to:

  • Make the layout, newline, and formatting style consistent with the rest of the Bond project (and with itself - some places use Allman style braces)
  • Move the imports below the license header
  • Add your details in the copyright at the top

Nice work! 👍

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