Using Multiple Uigesturerecognizers Simultaneously Like Uirotationgesturerecognizer & Uipangesturerecognizer in Swift 3

Simultaneously recognising UIPanGestureRecognizer, UIRotationGestureRecognizer, and UIPinchGestureRecognizer

Give this a try -- seems to work well for simultaneous Pan / Rotate / Scale:

class PinchPanRotateViewController: UIViewController, UIGestureRecognizerDelegate {

let testView: UILabel = {
let v = UILabel()
v.text = "TEST"
v.textAlignment = .center
v.textColor = .yellow

v.layer.cornerRadius = 4.0
v.layer.borderWidth = 4.0
v.layer.borderColor = UIColor.red.cgColor
v.layer.masksToBounds = true

//Enable multiple touch and user interaction
v.isUserInteractionEnabled = true
v.isMultipleTouchEnabled = true

return v
}()

override func viewDidLoad() {
super.viewDidLoad()

view.backgroundColor = .blue

view.addSubview(testView)
testView.frame = CGRect(x: 0, y: 0, width: 240, height: 180)
testView.center = view.center

//add pan gesture
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
gestureRecognizer.delegate = self
testView.addGestureRecognizer(gestureRecognizer)

//add pinch gesture
let pinchGesture = UIPinchGestureRecognizer(target: self, action:#selector(handlePinch(_:)))
pinchGesture.delegate = self
testView.addGestureRecognizer(pinchGesture)

//add rotate gesture.
let rotate = UIRotationGestureRecognizer.init(target: self, action: #selector(handleRotate(_:)))
rotate.delegate = self
testView.addGestureRecognizer(rotate)
}

@objc func handlePan(_ pan: UIPanGestureRecognizer) {
if pan.state == .began || pan.state == .changed {
guard let v = pan.view else { return }
let translation = pan.translation(in: self.view)
v.center = CGPoint(x: v.center.x + translation.x, y: v.center.y + translation.y)
pan.setTranslation(CGPoint.zero, in: self.view)
}
}

@objc func handlePinch(_ pinch: UIPinchGestureRecognizer) {
guard let v = pinch.view else { return }
v.transform = v.transform.scaledBy(x: pinch.scale, y: pinch.scale)
pinch.scale = 1
}

@objc func handleRotate(_ rotate: UIRotationGestureRecognizer) {
guard let v = rotate.view else { return }
v.transform = v.transform.rotated(by: rotate.rotation)
rotate.rotation = 0
}

func gestureRecognizer(_: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith shouldRecognizeSimultaneouslyWithGestureRecognizer:UIGestureRecognizer) -> Bool {
return true
}

}

A way to add Instagram like layout guide with UIPinchGestureRecognizer UIRotationGestureRecognizer & UIPanGestureRecognizer?

Below you will find an updated version of your class that should do what you describe.

Most of the updated code is located at the last section (Guides) near the end, but I have updated your UIGestureRecognizer actions a bit as well as your main init method.

Features:

- A vertical guide for centering a view's position horizontally.

- A horizontal guide for centering a view's rotation at 0 degrees.

- Position and rotation snapping to guides with tolerance values (snapToleranceDistance and snapToleranceAngle properties).

- Animated appearance / disappearance of guides (animateGuides and guideAnimationDuration properties).

- Guide views that can be changed per use case (movementGuideView and rotationGuideView properties)

class SnapGesture: NSObject, UIGestureRecognizerDelegate {

// MARK: - init and deinit
convenience init(view: UIView) {
self.init(transformView: view, gestureView: view)
}

init(transformView: UIView, gestureView: UIView) {
super.init()

self.addGestures(v: gestureView)
self.weakTransformView = transformView

guard let transformView = self.weakTransformView, let superview = transformView.superview else {
return
}

// This is required in order to be able to snap the view to center later on,
// using the `tx` property of its transform.
transformView.center = superview.center
}
deinit {
self.cleanGesture()
}

// MARK: - private method
private weak var weakGestureView: UIView?
private weak var weakTransformView: UIView?

private var panGesture: UIPanGestureRecognizer?
private var pinchGesture: UIPinchGestureRecognizer?
private var rotationGesture: UIRotationGestureRecognizer?

private func addGestures(v: UIView) {

panGesture = UIPanGestureRecognizer(target: self, action: #selector(panProcess(_:)))
v.isUserInteractionEnabled = true
panGesture?.delegate = self // for simultaneous recog
v.addGestureRecognizer(panGesture!)

pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(pinchProcess(_:)))
//view.isUserInteractionEnabled = true
pinchGesture?.delegate = self // for simultaneous recog
v.addGestureRecognizer(pinchGesture!)

rotationGesture = UIRotationGestureRecognizer(target: self, action: #selector(rotationProcess(_:)))
rotationGesture?.delegate = self
v.addGestureRecognizer(rotationGesture!)

self.weakGestureView = v
}

private func cleanGesture() {
if let view = self.weakGestureView {
//for recognizer in view.gestureRecognizers ?? [] {
// view.removeGestureRecognizer(recognizer)
//}
if panGesture != nil {
view.removeGestureRecognizer(panGesture!)
panGesture = nil
}
if pinchGesture != nil {
view.removeGestureRecognizer(pinchGesture!)
pinchGesture = nil
}
if rotationGesture != nil {
view.removeGestureRecognizer(rotationGesture!)
rotationGesture = nil
}
}
self.weakGestureView = nil
self.weakTransformView = nil
}

// MARK: - API

private func setView(view:UIView?) {
self.setTransformView(view, gestgureView: view)
}

private func setTransformView(_ transformView: UIView?, gestgureView:UIView?) {
self.cleanGesture()

if let v = gestgureView {
self.addGestures(v: v)
}
self.weakTransformView = transformView
}

open func resetViewPosition() {
UIView.animate(withDuration: 0.4) {
self.weakTransformView?.transform = CGAffineTransform.identity
}
}

open var isGestureEnabled = true

// MARK: - gesture handle

// location will jump when finger number change
private var initPanFingerNumber:Int = 1
private var isPanFingerNumberChangedInThisSession = false
private var lastPanPoint:CGPoint = CGPoint(x: 0, y: 0)
@objc func panProcess(_ recognizer:UIPanGestureRecognizer) {
guard isGestureEnabled, let view = self.weakTransformView else { return }

// init
if recognizer.state == .began {
lastPanPoint = recognizer.location(in: view)
initPanFingerNumber = recognizer.numberOfTouches
isPanFingerNumberChangedInThisSession = false
}

// judge valid
if recognizer.numberOfTouches != initPanFingerNumber {
isPanFingerNumberChangedInThisSession = true
}

if isPanFingerNumberChangedInThisSession {
hideGuidesOnGestureEnd(recognizer)
return
}

// perform change
let point = recognizer.location(in: view)
view.transform = view.transform.translatedBy(x: point.x - lastPanPoint.x, y: point.y - lastPanPoint.y)
lastPanPoint = recognizer.location(in: view)

updateMovementGuide()
hideGuidesOnGestureEnd(recognizer)
}

private var lastScale:CGFloat = 1.0
private var lastPinchPoint:CGPoint = CGPoint(x: 0, y: 0)
@objc func pinchProcess(_ recognizer:UIPinchGestureRecognizer) {
guard isGestureEnabled, let view = self.weakTransformView else { return }

// init
if recognizer.state == .began {
lastScale = 1.0;
lastPinchPoint = recognizer.location(in: view)
}

// judge valid
if recognizer.numberOfTouches < 2 {
lastPinchPoint = recognizer.location(in: view)
hideGuidesOnGestureEnd(recognizer)
return
}

// Scale
let scale = 1.0 - (lastScale - recognizer.scale);
view.transform = view.transform.scaledBy(x: scale, y: scale)
lastScale = recognizer.scale;

// Translate
let point = recognizer.location(in: view)
view.transform = view.transform.translatedBy(x: point.x - lastPinchPoint.x, y: point.y - lastPinchPoint.y)
lastPinchPoint = recognizer.location(in: view)

updateMovementGuide()
hideGuidesOnGestureEnd(recognizer)
}

@objc func rotationProcess(_ recognizer: UIRotationGestureRecognizer) {
guard isGestureEnabled, let view = self.weakTransformView else { return }

view.transform = view.transform.rotated(by: recognizer.rotation)
recognizer.rotation = 0
updateRotationGuide()
hideGuidesOnGestureEnd(recognizer)
}

func hideGuidesOnGestureEnd(_ recognizer: UIGestureRecognizer) {
if recognizer.state == .ended {
showMovementGuide(false)
showRotationGuide(false)
}
}

// MARK:- UIGestureRecognizerDelegate Methods
func gestureRecognizer(_: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith shouldRecognizeSimultaneouslyWithGestureRecognizer:UIGestureRecognizer) -> Bool {
return true
}

// MARK:- Guides

var animateGuides = true
var guideAnimationDuration: TimeInterval = 0.3

var snapToleranceDistance: CGFloat = 5 // pts
var snapToleranceAngle: CGFloat = 1 // degrees
* CGFloat.pi / 180 // (converted to radians)

var movementGuideView: UIView = {
let view = UIView()
view.backgroundColor = UIColor.blue
return view
} ()

var rotationGuideView: UIView = {
let view = UIView()
view.backgroundColor = UIColor.red
return view
} ()

// MARK: Movement guide and snap

func updateMovementGuide() {
guard let transformView = weakTransformView, let superview = transformView.superview else {
return
}

let transformX = transformView.frame.midX
let superX = superview.bounds.midX

if transformX - snapToleranceDistance < superX && transformX + snapToleranceDistance > superX {
transformView.transform.tx = 0
showMovementGuide(true)
} else {
showMovementGuide(false)
}

updateGuideFrames()
}

var isShowingMovementGuide = false

func showMovementGuide(_ shouldShow: Bool) {
guard isShowingMovementGuide != shouldShow,
let transformView = weakTransformView,
let superview = transformView.superview
else { return }

superview.insertSubview(movementGuideView, belowSubview: transformView)
movementGuideView.frame = CGRect(
x: superview.frame.midX,
y: 0,
width: 1,
height: superview.frame.size.height
)

let duration = animateGuides ? guideAnimationDuration : 0
isShowingMovementGuide = shouldShow
UIView.animate(withDuration: duration) { [weak self] in
self?.movementGuideView.alpha = shouldShow ? 1 : 0
}
}

// MARK: Rotation guide and snap

func updateRotationGuide() {
guard let transformView = weakTransformView else {
return
}

let angle = atan2(transformView.transform.b, transformView.transform.a)
if angle > -snapToleranceAngle && angle < snapToleranceAngle {
transformView.transform = transformView.transform.rotated(by: angle * -1)
showRotationGuide(true)
} else {
showRotationGuide(false)
}
}

var isShowingRotationGuide = false

func showRotationGuide(_ shouldShow: Bool) {
guard isShowingRotationGuide != shouldShow,
let transformView = weakTransformView,
let superview = transformView.superview
else { return }

superview.insertSubview(rotationGuideView, belowSubview: transformView)

let duration = animateGuides ? guideAnimationDuration : 0
isShowingRotationGuide = shouldShow
UIView.animate(withDuration: duration) { [weak self] in
self?.rotationGuideView.alpha = shouldShow ? 1 : 0
}
}

func updateGuideFrames() {
guard let transformView = weakTransformView,
let superview = transformView.superview
else { return }

rotationGuideView.frame = CGRect(
x: 0,
y: transformView.frame.midY,
width: superview.frame.size.width,
height: 1
)
}
}

For anyone interested, here's a test project using this class.

Simultaneous gesture recognition for specific gestures

Make sure your class implements UIGestureRecognizerDelegate

class YourViewController: UIViewController, UIGestureRecognizerDelegate ...

Set the gesture's delegate to self

yourGesture.delegate = self

Add delegate function to your class

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if (gestureRecognizer is UIPanGestureRecognizer || gestureRecognizer is UIRotationGestureRecognizer) {
return true
} else {
return false
}
}

Handling Multiple GestureRecognizers

The UIGestureRecognizerDelegate has a special function managing simultaneous recognition of several gestures on the same object, that will do the trick.

1) Set your UIViewController to conform UIGestureRecognizerDelegate

2) Implement the following function:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {

if (gestureRecognizer == mainScene.panRecognizer || gestureRecognizer == mainScene.pinchRecognizer) && otherGestureRecognizer == mainScene.tapRecognizer {
return true
}
return false
}

In this particular example we allow the tap gesture to get triggered simultaneously with panning and pinching.

3) Then just assign the delegates to the pan and pinch gesture recognizers:

override func viewDidLoad() {
// your code...

// Set gesture recognizers delegates
mainScene.panRecognizer.delegate = self
mainScene.pinchRecognizer.delegate = self
}

Handle two Gesture Recognizer simultaneously

With the help of Mitesh Mistri I got it working. One more thing that was missing was a function to disable the panGR inside the tableView because otherwise the user wouldnt be able to scroll. These are the two function that made it work:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if self.theTableView.view.bounds.contains(touch.location(in: self.theTableView.view)) {
return false
}
return true
}

Two UIGestureRecognizers

Since you want to kill the secondary gesture only when the primary gesture has ended or cancelled, do this in the gesture handler of the primary gesture.

- (void)handleGesture:(UIGestureRecognizer*)gesture {
...
if ( gesture.state == UIGestureRecognizerStateEnded || gesture.state == UIGestureRecognizerCancelled ) {
secondaryGesture.enabled = NO;
secondaryGesture.enabled = YES;
}
}

This seems to be the only way you can cancel a gesture.


You can use requireGestureRecognizerToFail: to declare a dependency.

[secondaryGesture requireGestureRecognizerToFail:primaryGesture];

This will kill the secondary gesture on successful identification of the primary gesture. There is no such tool provided if the primary gesture is cancelled. You can probably flip the enabled flag of the secondary gesture to NO and YES in the gesture handler of the primary gesture on UIGestureRecognizerStateCancelled but that doesn't seem elegant.

Pinch, Pan, and Rotate Text Simultaneously like Snapchat [SWIFT 3]

By default, after one gesture recognizer on a view starts handling the gesture, other recognizers are ignored. In Swift, this behaviour can be controlled by overriding the method,

gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:)^1,

to return true. The default implementation returns false.

To override the function just add your implementation, returning true, to your ViewController source code file. Here is some sample Swift code:

class ViewController: UIViewController,UIGestureRecognizerDelegate {

@IBOutlet var textField: UITextField!

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.

//add pan gesture
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
gestureRecognizer.delegate = self
textField.addGestureRecognizer(gestureRecognizer)

//Enable multiple touch and user interaction for textfield
textField.isUserInteractionEnabled = true
textField.isMultipleTouchEnabled = true

//add pinch gesture
let pinchGesture = UIPinchGestureRecognizer(target: self, action:#selector(pinchRecognized(pinch:)))
pinchGesture.delegate = self
textField.addGestureRecognizer(pinchGesture)

//add rotate gesture.
let rotate = UIRotationGestureRecognizer.init(target: self, action: #selector(handleRotate(recognizer:)))
rotate.delegate = self
textField.addGestureRecognizer(rotate)

}

func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
if gestureRecognizer.state == .began || gestureRecognizer.state == .changed {

let translation = gestureRecognizer.translation(in: self.view)
// note: 'view' is optional and need to be unwrapped
gestureRecognizer.view!.center = CGPoint(x: gestureRecognizer.view!.center.x + translation.x, y: gestureRecognizer.view!.center.y + translation.y)
gestureRecognizer.setTranslation(CGPoint.zero, in: self.view)
}

}

func pinchRecognized(pinch: UIPinchGestureRecognizer) {

if let view = pinch.view {
view.transform = view.transform.scaledBy(x: pinch.scale, y: pinch.scale)
pinch.scale = 1
}
}

func handleRotate(recognizer : UIRotationGestureRecognizer) {
if let view = recognizer.view {
view.transform = view.transform.rotated(by: recognizer.rotation)
recognizer.rotation = 0
}
}

//MARK:- UIGestureRecognizerDelegate Methods
func gestureRecognizer(_: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith shouldRecognizeSimultaneouslyWithGestureRecognizer:UIGestureRecognizer) -> Bool {
return true
}
}

Here's the crucial override in Objective-C^2:

-(BOOL)gestureRecognizer:(UIGestureRecognizer*)aR1 shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)aR2
{
return YES;
}


Related Topics



Leave a reply



Submit