I have a single view on my iOS application, with a mapView in it.
When adding a tap or long press recognizer, the events are properly called.
But not with the pinch event...
UIPinchGestureRecognizer *handlePinchGesture=[[UIPinchGestureRecognizer alloc]initWithTarget:mapView action:#selector(handleGesture:)];
[mapView addGestureRecognizer:handlePinchGesture];
Any idea what I should add ?
Thanks.
Assuming your mapView is an MKMapView, it has its own pinch gesture recognizer for zooming the map.
If you want to add your own recognizer, you have to allow it to recognize simultaneously with the other (mapview-controlled) recognizer. Set your gesture recognizer's delegate and implement gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: (you can just always return YES).
You should also probably set self as the gesture recognizer's target and not the mapView.
In the handleGesture method did you do something like this:
CGFloat beginPinch; //declare this as your ivars
-(void)handleGesture:(UIPinchGestureRecognizer *)pinchRecognizer
{
if (pinchRecognizer.state == UIGestureRecognizerStateBegan)
{
beginPinch = pinchRecognizer.scale;
}
else if (pinchRecognizer.state == UIGestureRecognizerStateEnded)
{
if (pinchRecognizer.scale < beginPinch)
{
//do your stuff
}
}
}
Related
I have a Subview A(self.thumbnailImageView) added to my superview. I have added a UILongPressGestureRecognizer and UISwipeGestureRecognizer to my subview A.
[self addLongPressGestureRecognizerForPreviewCell:self.thumbnailImageView];
[self addSwipeUpGestureRecognizerForImageView:self.thumbnailImageView];
Now in the handler methods, when the UILongPressGestureRecognizer state begins, i add a Subview B(bigPreviewImage) to my superview(self.view).
-(void)tapGesture:(UILongPressGestureRecognizer *)recognizer{
if (recognizer.state == UIGestureRecognizerStateBegan)
{
// Long press detected, start the timer
[self showPreviewImage:recognizer];
}
else if(recognizer.state == UIGestureRecognizerStateEnded)
{
[self hidePreviewImage];
}
}
-(void)showPreviewImage:(UILongPressGestureRecognizer *)recognizer{
UIImageView *bigPreviewImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Desktop"]];
bigPreviewImage.frame = CGRectMake(self.thumbnailImageView.frame.origin.x - 50.0, self.thumbnailImageView.frame.origin.y + self.thumbnailImageView.frame.size.height + 10.0, 300.0, 250.0);
bigPreviewImage.tag = 10000;//200.0 & 125.0
[bigPreviewImage setUserInteractionEnabled:YES];
[self.view addSubview:bigPreviewImage];
}
Now once the UILongPressGestureRecognizer is active and the user with his finger still pressing the Subview A, If the user swipes up the screen, i want the SwipeGestureRecognizer to get initiated. But the same is not happening. How to initiate a gesture recognizer when a gesture is already active?
I have implemented the shouldRecognizeSimultaneouslyWithGestureRecognizer method but still the swipe up gesture method is not called. Please let me know if i am missing something.
Got it!!!
We dont need a separate Swipe Gesture Recognizer. The different states in Long Press Gesture Recognizer can be used to handle this scenario.
Long Press Gesture has different states Like UIGestureRecognizerStateBegan, UIGestureRecognizerStateChanged and UIGestureRecognizerStateEnded.
UIGestureRecognizerStateBegan gets called as soon as you long press the subview.
UIGestureRecognizerStateChanged gets called when the user tries to move the finger .
UIGestureRecognizerStateEnded gets called when the user lifts the finger from the touch point.
-(void)longPressGestureForPreviewImageView:(UILongPressGestureRecognizer *)recognizer{
if (recognizer.state == UIGestureRecognizerStateBegan)
{
// Long press detected, start the timer
[self showPreviewImage:recognizer];
}
else if(recognizer.state == UIGestureRecognizerStateChanged)
{
NSLog(#"Swipe up");
if ([self.thumbnailImageView.gestureRecognizers containsObject:recognizer]) {
[self.thumbnailImageView removeGestureRecognizer:recognizer];
}
}
else if(recognizer.state == UIGestureRecognizerStateEnded)
{
[self hidePreviewImage];
}
So we can use the Gesture Delegate methods to handle the swipe along with a Long Press Gesture Recognizer.
I'd like to enable UIPanGestureRecognizer on customView when the customView did longPress.
(I wish when you longPress customView, the customView will switch to "move mode", and you can move the customView by drag.)
But in this code, only longPressAction: called. panAction: did not called.
How do I fix it to enable PanAction:?
- (void)viewDidLoad
{
[self.view addSubview:customView];
UILongPressGestureRecognizer *longPressRecognizer = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:#selector(longPressAction:)];
[customView addGestureRecognizer:longPressRecognizer];
UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc]initWithTarget:self action:#selector(panAction:)];
[customView addGestureRecognizer:panRecognizer];
}
- (void)longPressAction:(UILongPressGestureRecognizer *)recognizer
{
if ([recognizer state] == UIGestureRecognizerStateBegan) {
CustomView *customView = (CustomView *)recognizer.view;
customView.panRecongnizerEnabled = YES; //panRecongnizerEnabled is CustomView's property
}
if ([recognizer state] == UIGestureRecognizerStateEnded) {
CustomView *customView = (CustomView *)recognizer.view;
customView.panRecongnizerEnabled = NO;
}
}
- (void)panAction:(UIPanGestureRecognizer *)recognizer
{
CustomView *customView = (CustomView *)recognizer.view;
if (customCell.panRecongnizerEnabled == NO) return;
NSLog(#"running panAction");
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
Your ViewController needs to conform to the UIGestureRecognizerDelegate. I suspect you either already did that or otherwise the shouldRecognizeSimultaneouslyWithGestureRecognizer would not make any sense. But what you are definitely missing is setting the gestureRecognizer´s delegate to your viewController:
longPressRecognizer.delegate = self;
panRecognizer.delegate = self;
Now you should be receiving both long press and pan simultaneous.
Note: I tested without any customView, just added them to self.view. At least in that case, the code above worked as expected.
I know this question has been closed for a while, but I recently had to do something similar, and there is a much cleaner solution. The UILongPressGestureRecognizer is all you need to do this.
Long-press gestures are continuous. The gesture begins
(UIGestureRecognizerStateBegan) when the number of allowable fingers
(numberOfTouchesRequired) have been pressed for the specified period
(minimumPressDuration) and the touches do not move beyond the
allowable range of movement (allowableMovement). The gesture
recognizer transitions to the Change state whenever a finger moves,
and it ends (UIGestureRecognizerStateEnded) when any of the fingers
are lifted.
Because of this, for what you need, there is no need for the UIPanGestureRecognizer. Simply add the UILongPressGestureRecognizer and attach it to your main view.
- (void)viewDidLoad {
[super viewDidLoad];
UILongPressGestureRecognizer *longPressRecognizer = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:#selector(longPressAction:)];
// set this to your desired delay before the pan action will start.
longPressRecognizer.minimumPressDuration = 0.5;
[self.view addGestureRecognizer:longPressRecognizer];
}
Then, implement the longPressAction method to look at not just UIGestureRecognizerStateBegan or UIGestureRecognizerStateEnded states, but also the UIGestureRecognizerStateChanged state:
- (void)longPressAction:(UIGestureRecognizer *)recognizer
{
if ([recognizer state] == UIGestureRecognizerStateBegan || [recognizer state] == UIGestureRecognizerStateChanged ) {
CGPoint touchPoint = [recognizer locationInView:self.view];
[self.viewToMove setCenter: touchPoint];
NSLog(#"running panAction");
}
}
In my example, I simply move a dummy view to track the center of the user's finger, but you can put any logic you would have put in the UIPanGestureRecognizer's code. Simply put it inside the if block and it greatly simplifies your code and you don't need to deal with interactions between the two gesture recognizers. Your code would also result in many needless calls to the panAction method when the user is simply moving their finger around the screen (but hasn't done a long press).
You can do it by enabling the pan after the long press. Anyhow you will have to save a flag to know if the longPress was called. And in order to enable the pan gesture after the longPress, you can play with the pan gesture delegate, as following:
let previewLongPress = UILongPressGestureRecognizer(target: self, action: #selector(onLongPressed(sender:)))
let previewPanGesture = UIPanGestureRecognizer(target: self, action: #selector(onPanGesture(sender:)))
previewPanGesture?.delegate = self
Your delegate should seems like this:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return gestureRecognizer == previewPanGesture && otherGestureRecognizer == previewLongPress
}
And now you're able to have a pan gesture after a long press gesture.
Hope it helps other people.
I need two functions which would be fired when the rotation gesture starts and finishes, because I need to know the whole angle of the rotation. Currently the gesture recogniser is fired all the time until the rotation finishes, and I cannot find out when it has finished, to find to total angle.
That's because the method you hook to your gesture gets called for all of the gestures states, like began/ended/canceled/changed. You can however ask the gesture for its current state within the method, and add specific functionality for these different states. Here's a basic example:
- (void)rotationGestureHandler:(UIRotationGestureRecognizer *)gesture
{
if (gesture.state == UIGestureRecognizerStateBegan) {
// do stuff - call method for gesture began
}else if (gesture.state == UIGestureRecognizerStateEnded) {
// do other stuff - call method for gesture ended
}
}
I am using a UIPinchGestureRecognizer, which uses 2 fingers by default. If a user decides to perform the multitask gesture, the pinch gestures action is also activated.
Is there a way to cancel the pinch gesture from occurring if more than four UITouch instances are detected?
Edit Removed sample code as it was the wrong approach.
Since you're not subclassing the UIPinchGestureRecognizer, you shouldn't be using touchBegan:withEvent:. Instead you should be handling it in the method that is called when a pinch occurs.
- (void)handlePinch:(UIPinchGestureRecognizer *)pinchGestureRecognizer
{
// if there are 2 fingers being used
if ([pinchGestureRecognizer numberOfTouches] == 2) {
// do stuff
}
}
With a multitask gesture, the numberOfTouches returned by the UIPinchGestureRecognizer is 2 instead of 4 or 5, because some touches are ignored.
You can subclass UIPinchGestureRecognizer and override ignoreTouch:forEvent to cancel the recognizer if the event has 4 or 5 touches:
- (void) ignoreTouch:(UITouch*)touch forEvent:(UIEvent*)event
{
[super ignoreTouch:touch forEvent:event];
// Cancel recognizer during a multitask gesture
if ([[event allTouches] count] > 3)
{
self.state = UIGestureRecognizerStateCancelled;
}
}
I have the following problem.
I am using a UILongPressGestureRecognizer to put a UIView into a "toggle mode". If the UIView is in "toggle mode" the user is able to drag the UIView around the screen. For dragging the UIView around the screen I am using the methods touchesBegan, touchesMoved and touchesEnded.
It works, but: I have to lift my finger in order to drag it, because the touchesBegan method got already called and therefore is not called again and therefore I can't drag the UIView around the screen.
Is there any way to manually call touchesBegan after UILongPressGestureRecognizer got triggered (UILongPressGestureRecognizer changes a BOOL value and the touchesBegan only works if this BOOL is set to YES).
UILongPressGestureRecognizer is a continuous gesture recognizer, so rather than resorting to touchesMoved or UIPanGestureRecognizer, just check for UIGestureRecognizerStateChanged, e.g.:
- (void)viewDidLoad
{
[super viewDidLoad];
UILongPressGestureRecognizer *gesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
[self.view addGestureRecognizer:gesture];
}
- (void)handleGesture:(UILongPressGestureRecognizer *)gesture
{
CGPoint location = [gesture locationInView:gesture.view];
if (gesture.state == UIGestureRecognizerStateBegan)
{
// user held down their finger on the screen
// gesture started, entering the "toggle mode"
}
else if (gesture.state == UIGestureRecognizerStateChanged)
{
// user did not lift finger, but now proceeded to move finger
// do here whatever you wanted to do in the touchesMoved
}
else if (gesture.state == UIGestureRecognizerStateEnded)
{
// user lifted their finger
// all done, leaving the "toggle mode"
}
}
I would suggest you to use UIPanGestureRecognizer as it a recommended gesture for dragging.
You can configure the min. and max. number of touches required for a panning, using the following the properties:
maximumNumberOfTouches
minimumNumberOfTouches
You can handle the states like Began, Changed and Ended, like having animation for the required states.
Using the below method translate the point to the UIView in which you want it.
- (void)setTranslation:(CGPoint)translation inView:(UIView *)view
example:
You have to use a global variable to retain the old frame. Get this in UIGestureRecognizerStateBegan.
When the state is UIGestureRecognizerStateChanged. You can use the
-(void) pannningMyView:(UIPanGestureRecognizer*) panGesture{
if(panGesture.state==UIGestureRecognizerStateBegan){
//retain the original state
}else if(panGesture.state==UIGestureRecognizerStateChanged){
CGPoint translatedPoint=[panGesture translationInView:self.view];
//here you manage to get your new drag points.
}
}
Velocity of the drag. Based on the velocity you can provide a animation to show bouncing of a UIView
- (CGPoint)velocityInView:(UIView *)view