Skip to content

Instantly share code, notes, and snippets.

@samuelbeek
Created January 4, 2017 10:40
Show Gist options
  • Save samuelbeek/f7d9f7f2d24b8999251cc43dbe76cc01 to your computer and use it in GitHub Desktop.
Save samuelbeek/f7d9f7f2d24b8999251cc43dbe76cc01 to your computer and use it in GitHub Desktop.
Swift 3 implementation of Kraken Devs' force touch recognizer
// Source: https://krakendev.io/force-touch-recognizers/
// Without this import line, you'll get compiler errors when implementing your touch methods since they aren't part of the UIGestureRecognizer superclass
import UIKit.UIGestureRecognizerSubclass
//Since 3D Touch isn't available before iOS 9, we can use the availability APIs to ensure no one uses this class for earlier versions of the OS.
@available(iOS 9.0, *)
public class ForceTouchGestureRecognizer: UIGestureRecognizer {
//Since it also doesn't make sense to have our force variable as a settable property, I'm using a private instance variable to make our public force property read-only
private var _force: CGFloat = 0.0
//Because we don't know what the maximum force will always be for a UITouch, the force property here will be normalized to a value between 0.0 and 1.0.
public var force: CGFloat { get { return _force } }
public var maximumForce: CGFloat = 4.0
//We override the initializer because UIGestureRecognizer's cancelsTouchesInView property is true by default. If you were to, say, add this recognizer to a tableView's cell, it would prevent didSelectRowAtIndexPath from getting called. Thanks for finding this bug, Jordan Hipwell!
override init(target: Any?, action: Selector?) {
super.init(target: target, action: action)
cancelsTouchesInView = false
}
override public func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
normalizeForceAndFireEvent(state: .began, touches: touches)
}
override public func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesMoved(touches, with: event)
normalizeForceAndFireEvent(state: .changed, touches: touches)
}
override public func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesEnded(touches, with: event)
normalizeForceAndFireEvent(state: .ended, touches: touches)
}
override public func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesCancelled(touches, with: event)
normalizeForceAndFireEvent(state: .cancelled, touches: touches)
}
func normalizeForceAndFireEvent(state: UIGestureRecognizerState, touches: Set<UITouch>) {
//Putting a guard statement here to make sure we don't fire off our target's selector event if a touch doesn't exist to begin with.
guard let firstTouch = touches.first else {
return
}
//Just in case the developer set a maximumForce that is higher than the touch's maximumPossibleForce, I'm setting the maximumForce to the lower of the two values.
maximumForce = min(firstTouch.maximumPossibleForce, maximumForce)
//Now that I have a proper maximumForce, I'm going to use that and normalize it so the developer can use a value between 0.0 and 1.0.
_force = firstTouch.force / maximumForce
//Our properties are now ready for inspection by the developer. By setting the UIGestureRecognizer's state property, the system will automatically send the target the selector message that this recognizer was initialized with.
self.state = state
}
//This function is called automatically by UIGestureRecognizer when our state is set to .Ended. We want to use this function to reset our internal state.
public override func reset() {
super.reset()
_force = 0.0
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment