I have a 2d map that the user can zoom and pan using the gesture recognizers. While it works, i want the user to start panning immediately after a zoom once they have 1 finger lifted. Unfortunately, in the docs it says:
The gesture ends (UIGestureRecognizerStateEnded) when both fingers
lift from the view.
which is pretending me from going from a pinch zoom to a pan right away. What could i do to fix this?
This is possible, and easy! It involves being your gesture recognizer's delegate. Something no-one seems to know exists. In my view controller subclass I have declared both conforming to the protocol <UIGestureRecognizerDelegate> and two ivars:
UIPinchGestureRecognizer *myPinchGR;
UIPanGestureRecognizer *myPanGR;
These ivars are instantiated in view did load. Notice setting self as the delegate.
-(void)viewDidLoad{
[super viewDidLoad];
myPanGR = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panTarget:)];
myPanGR.delegate = self;
[self.view addGestureRecognizer:myPanGR];
myPinchGR = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(pinchTarget:)];
myPinchGR.delegate = self;
[self.view addGestureRecognizer:myPinchGR];
}
One of the delegate calls made by a UIGestureRecognizer is shouldRecognizeSimultaneouslyWithGestureRecognizer: if I had more than two gesture recognizers then this function would have to contain some logic. But since there are only two I can just return YES.
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
Now you do have to include a little (very little) extra logic in your action methods to screen for the appropriate conditions.
-(void)panTarget:(UIPanGestureRecognizer *)panGR{
if (panGR.numberOfTouches > 1) return;
NSLog(#"panny");
}
-(void)pinchTarget:(UIPinchGestureRecognizer *)pinchGR{
if (pinchGR.numberOfTouches < 2) return;
NSLog(#"pinchy");
}
Run this code an look at the logs. you will see when you move one finger you will see "panny" when you place a second finger down you will see "pinchy", and back and forth.
Use this code inside the gesture handling method.
if (gesture.numberOfTouches != 2) {
// code here to end pinching
}
As Gesture handling method will be called immediately when user lift a finger while 2 finger pinching.
Related
I have added a pinch gesture recognizer to a scroll view, using it to close a modal view controller. I did it like so:
UIPinchGestureRecognizer *closePinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(closeGallery)];
[galleryScrollView addGestureRecognizer:closePinch];
Although it is added to a scrollView, I do not actually use it to zoom only to close the view. Therefore, I have no need for the pinch-out gesture as it indicates zooming in.
Is there a way to easily disable the pinch-out portion of the gesture recognizer and leave the pinch-in untouched?
Based on Crazyrems' answer, the following delegate method did exactly what I needed:
- (BOOL)gestureRecognizerShouldBegin:(UIPinchGestureRecognizer *)gestureRecognizer
{
// Negative velocity indicates pinch out
if (gestureRecognizer.velocity < 0) {
return YES; // <- Register touch event
} else {
return NO; // <- Do not register touch event
}
}
You should implement -gestureRecognizerShouldBegin: in your UIGestureRecognizerDelegate
There's a velocity property in the recognizer passed in parameter, so you can check if it's a pinch in or out, and return YES or NO in consequence.
I want the ability to change how the playback controls are presented while using the new AVPlayerViewController in AVKit. Basically, I want to overide the single finger tap gesture to do something else, and replace that gesture with a double finger tap. I am subclassing AVPlayerViewController to add this functionality.
I can add the double finger tap easily by creating a new UITapGestureRecognizer, but doing so with a single tap does nothing, as the playback controls still appear and my custom gesture method is not called. I assume because the AVPlayerViewController has a gesture with priority that is called instead.
I setup the gestures like normal...
// singleFingerTap: will never fire...
UITapGestureRecognizer *singleFingerTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(singleFingerTap:)];
singleFingerTap.numberOfTouchesRequired = 1;
singleFingerTap.delegate = self;
[self.view addGestureRecognizer:singleFingerTap];
// doubleFingerTap: will work correctly...
UITapGestureRecognizer *doubleFingerTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(doubleFingerTap:)];
doubleFingerTap.numberOfTouchesRequired = 2;
doubleFingerTap.delegate = self;
[self.view addGestureRecognizer:doubleFingerTap];
Any thoughts on how to achieve this without accessing private properties? Is it even permitted? I know I can create my own view controller with an instance of AVPlayer and then create my own playback controls, but I'm hoping I can use the lightweight AVKit player here with a few simple modifications.
I've tried looping through the gestures in AVPlayerViewController's view and removing them, but the gestureRecognizers property was empty. Even if I could do this, I wouldn't know how to add back the gesture to display the playback controls on a double finger tap instead of the single finger tap.
Any suggestions would be much appreciated! Especially whether this is possible/allowed or not. Thanks!
EDIT:
I have found a way to explicitly unblock the player's private gesture that was blocking my own gesture.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
id firstGesture = gestureRecognizer;
id secondGesture = otherGestureRecognizer;
if ([firstGesture isKindOfClass:[UITapGestureRecognizer class]] && [secondGesture isKindOfClass:[UITapGestureRecognizer class]]) {
UITapGestureRecognizer *tapGesture = firstGesture;
UITapGestureRecognizer *otherTapGesture = secondGesture;
if (tapGesture.numberOfTapsRequired == otherTapGesture.numberOfTapsRequired && tapGesture.numberOfTouches == otherTapGesture.numberOfTouches) {
// Disable the single tap that shows the playback controls...
return YES;
}
}
return NO;
}
This effectively prevents the playback controls from appearing on a tap, and my singleTapGesture: fires as expected. However, I now have the issue of getting the playback controls to appear on a different gesture. Is it possible to reroute the private gesture, or simulate the private gesture programmatically?
Why not just check/modify the target/remove the default gesture recognizers first?
You can access them with the standard UIView's gestureRecognizers.
The gestures recognizers may be buried inside a private subview.
Another solution would be to set userInteractionEnabled to NO and add your gesture recognizers to the superview or to a "overlay" transparent view.
I have two subviews inside the view. The frame is equal. A view has a swipe left gesture recognizer and B view has a swipe right gesture recognizer. B is behind A so I can only trigger swipe left gesture. How can I receive both?
The view in the back will not respond because, well, it's in the back.
For both gestures to respond simultaneously (left+right at the same time), add a UIGestureRecognizerDelegate to either and handle shouldRecognizeSimultaneouslyWithGestureRecognizer as suggested below.
If you want B to respond to a gesture, even though it is partially hidden by A, rethink your hierarchy: add a 3rd view above A & B (call it C), same size as B, as a placeholder to your gesture recognizer.
Your delegate:
// leftGesture
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer
*)otherGestureRecognizer
{
return otherGestureRecognizer == rightGesture;
}
How can I receive both?
Add both gesture recognizers to view A, since that's the view in front. Since you need the right swipe gesture to be handled by view B, make view B the target for the right swipe gesture recognizer.
A better option might be to make your view controller the target of both gesture recognizers and let it sort out what to do with the user's gestures. That helps to get the views out of the business of knowing how the user interface is set up -- they just have to follow instructions from the view controller. If you adopt that plan, your view controller might look like this in part:
- (void)viewDidLoad
{
UISwipeGestureController *leftSwipe = [[UISwipeGestureController alloc] initWithTarget:self action:#selector(leftSwipe:)];
leftSwipe.direction = UISwipeGestureRecognizerDirectionLeft;
UISwipeGestureController *rightSwipe = [[UISwipeGestureController alloc] initWithTarget:self action:#selector(rightSwipe:)];
rightSwipe.direction = UISwipeGestureRecognizerDirectionRight;
[self.viewA addGestureRecognizer:leftSwipe];
[self.viewA addGestureRecognizer:rightSwipe];
}
- (IBAction)leftSwipe:(id)sender
{
[self.viewA doSomething];
}
- (IBAction)rightSwipe:(id)sender
{
[self.viewB doSomethingElse];
}
I would like to detect when a user makes a swipe gesture, and detect it's direction (left, right, up or down). I need to detect it not when the gesture is finished, but just when the iPhone knows the direction, even if it's not finished.
I am using XCode 5, and designing for iPhone 5 with iOS 7.
I would like to know the code that I have to paste to the .h, and to the .m, and if I have to drag and drop any item to the mainView.
If you want to know the direction when the gesture is not finished you would probably need the UIPanGestureRecognizer and detect the direction using the velocityInView: method.
in the *.h file:
#interface ViewController : UIViewController
#property (nonatomic, strong) UIPanGestureRecognizer *recognizer;
#end
in the *.m file:
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.recognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panGesture:)];
[self.view addGestureRecognizer:self.recognizer];
}
-(void)panGesture:(UIPanGestureRecognizer *)sender
{
NSLog(#"Velocity: %#", NSStringFromCGPoint([sender velocityInView:self.view]));
// Here You need to determine the direction
}
#end
From Docs:
Gestures are either discrete or continuous. A discrete gesture, such
as a tap, occurs once. A continuous gesture, such as pinching, takes
place over a period of time. For discrete gestures, a gesture
recognizer sends its target a single action message. A gesture
recognizer for continuous gestures keeps sending action messages to
its target until the multitouch sequence ends,
So if you need to "detect it not when the gesture is finished, but just when the iphone knows the direction, even if it's not finished" you could not use UISwipeGestureRecognizer since it is a discrete one. You should use UIPanGestureRecognizer and analyse it is results to check is there a swipe gesture or not.
To have a good example how it works, you can download a sample code provided by Apple:
https://developer.apple.com/library/ios/samplecode/SimpleGestureRecognizers/Introduction/Intro.html#//apple_ref/doc/uid/DTS40009460
Hope that helps ;)
You can easily get using UISwipeGestureRecognizers property directions...
After :----
UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:#selector(swipeRight)];
swipeRight.direction = UISwipeGestureRecognizerDirectionRight;
[self.yourView addGestureRecognizer:swipeRight];
UISwipeGestureRecognizer *swipeLeft = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:#selector(swipeLeft)];
swipeRight.direction = UISwipeGestureRecognizerDirectionLeft;
[self.gifWebView addGestureRecognizer:swipeLeft];
-(void)swipeRight{
//Do whatever you want
}
-(void)swipeLeft{
//Do whatever you want
}
And For Before Swipe you can use UIPanGestureRecognizer
I hope it help you
Swipe gestures are very quick. Swipe gesture recognizers are designed to fire once, not continuously. I don't think you will be able to get information about a swipe gesture that is "in flight" very easily.
If this is urgent, you will probably have to create a custom subclass of swipe gesture recognizer and add additional logic and delegate messages that would notify the delegate once the gesture recognizer decides that it has detected a swipe. That is way beyond your current skill level however.
Is there any way to get UITapGestureRecognizer to run on touch began?
I can't use touchesBegan because I am using a UITableView and the super view steals the event essentially.
I just want to detect when the screen is first touched. Why is this so difficult? Maybe I need a different solution than using tapgesturerecognizer?
You can use state property of UIGestureRecognizer to identify various states of any gesture -
#property(nonatomic,readonly) UIGestureRecognizerState state; // the current state of the gesture recognizer
So when the gesture begin, use something like this in your registered handler method -
if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
// Do your stuff
}
You can add a tap gesture recognizer to the tableView in viewDidLoad like this:
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapGestureRecognized:)];
[self.tableView addGestureRecognizer:tapGestureRecognizer];
Then implement this method:
- (void)tapGestureRecognized:(UITapGestureRecognizer *)tapGestureRecognizer {
NSLog(#"tap gesture recognized");
}
Just tested this out, and works fine. For every tap i get the log message on my console. Note that this prevents the tableview from receiving the taps, other gestures will just be handled by the table view as usual.
You need to set delaysContentTouches = NO