UITapGestureRecognizer causes crash after dismissing viewController - ios

Trying to implement a UITapGestureRecognizer to a form sheet modal viewcontroller.
If user touches outside of the form sheet, form sheet should dismiss, it does so code works just fine.
Problem is If I manually dismiss the form sheet and try to touch any point in view it still tries to call UITapGestureRecognizer method and app crashes.
Error ::
-[xxxxView handleTapBehind:]: message sent to deallocated instance
-(void)done
{
[self.navigationController popViewControllerAnimated:YES];
//send notification that folder has been created
[[NSNotificationCenter defaultCenter] postNotificationName:#"refreshDetails" object:nil];
}
-(void)viewDidAppear:(BOOL)animated
{
// gesture recognizer to cancel screen
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapBehind:)];
[recognizer setNumberOfTapsRequired:1];
recognizer.cancelsTouchesInView = NO; //So the user can still interact with controls in the modal view
[self.view.window addGestureRecognizer:recognizer];
}
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
CGPoint location = [sender locationInView:nil]; //Passing nil gives us coordinates in the window
//Then we convert the tap's location into the local view's coordinate system, and test to see if it's in or outside. If outside, dismiss the view.
if (![self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil])
{
// Remove the recognizer first so it's view.window is valid.
[self.view.window removeGestureRecognizer:sender];
[self dismissModalViewControllerAnimated:YES];
}
}
}
Why is handleTapBehind: still called after I dismiss viewcontroller? How can I fix this?

You add gesture recognizer to window:
[self.view.window addGestureRecognizer:recognizer];
And set target to your controller;
So, when your controller closed - it's released, but gesture recognizer still alive. And when it fire it try to send action to your controller, which already does not exists.
So, you should add recognizer to controller's view or remove him in viewWillDissaper method.

Try using gesture recognizer's delegate method, put a flag when you manually dismissed the vc and in shouldReceiveTouch:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if(flag)
{
return NO;
}
return YES;
}
Don't forget to set the delegate as the current viewcontroller. Also you can now remove the line [self.view.window removeGestureRecognizer:sender];

I will like to add the code from #mikhail 's answer
(UITapGestureRecognizer *)senderTap
-(void)viewWillDisappear:(BOOL)animated{
[self.view.window removeGestureRecognizer:senderTap];
}
In my code I capture UITapGestureRecognizer (sender) in a selector method when ViewDidAppear is called.

Related

interactivePopGestureRecognizer some time freez view touch and it will also cause [viewcontroller hash]: message sent to deallocated instance?

I have added 'interactivePopGestureRecognizer' in some viewcontroller, also give back button on topbar . When user use Pop Gesture and push/pop view at that time it will freez top navigation controller view . After some time it will crash with "[viewcontroller hash]: message sent to deallocated instance."
METHOD
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
//Pop GESTURE
if ([self.navigationController respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.enabled = true;
self.navigationController.interactivePopGestureRecognizer.delegate = self;
}
-(void)viewDidDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
//Remove Pop Gesture
if ([self.navigationController respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.enabled = false;
self.navigationController.interactivePopGestureRecognizer.delegate = nil;
}
}
DELEGATE METHOD
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
// add whatever logic you would otherwise have
return YES;
}
I have TabBar type application.
The crash would be explained by you somehow failing to execute
self.navigationController.interactivePopGestureRecognizer.delegate = nil;
in viewDidDisappear. Can you confirm that your delegate is actually being removed by stepping through it in the debugger?

How to hide UINavigationItem on MKMapView pan gesture on iOS 8 like Lyft App?

Hello and thanks for checking out this question,
I am trying to emulate the functionality of the Lyft app.
In the Lyft app, when the user pans on the map, the bottom bar disappears as well as the UINavigationBar on top. I am trying to recreate this functionality in my own app
On iOS 8, there is a property on UINavigationController that lets you hide the top bar on scroll. This is nice, but can this same functionality be implemented on a mapview pan?
Also, in my app, I have a UITabBar. Does anyone know how to hide that as well??
Add pan gesture to mapview and set delegate.
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(pan:)];
pan.delegate = self;
[self.mapView addGestureRecognizer:pan];
Implement gesture delegate and return YES.
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
In gesture selector
-(void)pan:(UIPanGestureRecognizer*)ges
{
[self.navigationController setNavigationBarHidden:1 animated:1];
[self.tabBarController.tabBar setHidden:YES];
}
These delegate methods of MKMapView also look pretty helpful! This is great because you most likely have already implemented the MKMapView delegate !
- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
then you can hide
[self.navigationController setNavigationBarHidden:YES animated:YES];
[self.tabBarController.tabBar setHidden:YES];
and unhide
[self.navigationController setNavigationBarHidden:NO animated:YES];
[self.tabBarController.tabBar setHidden:NO];

how make view transition like iPad appstore on iOS 7

I want to make a view transition, like iPad AppStore on iOS 7, click a item(app) and popping a detail view with flip vertical, and tapping outside of detail view will dismiss, this view transition is neither like popover and modal transition.
So, how can I make this transition...
Appreciation for your help.
Also looking for the transition animation. But I did figure out how to accomplish the tapping outside of window to dismiss/close. It can be found here.
In your viewDidAppear you create a UITapGestureRecognizer and add it to the view's window. Like so:
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapBehind:)];
[recognizer setNumberOfTapsRequired:1];
recognizer.cancelsTouchesInView = NO;
[self.view.window addGestureRecognizer:recognizer];
handleTapBehind checks to see is the Gesture's state has ended and get's the location of the tap and dismisses the view/window
- (void)handleTapBehind:(UITapGestureRecognizer *)sender{
if (sender.state == UIGestureRecognizerStateEnded){
CGPoint location = [sender locationInView:nil]; //Passing nil gives us coordinates in the window
//Check to see if it's in or outside. If outside, dismiss the view.
if (![self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil]){
// Remove the recognizer first so it's view.window is valid.
[self.view.window removeGestureRecognizer:sender];
[self dismissViewControllerAnimated:YES completion:nil];
}
}
}
Hope this helps!
Start here. This is an example:
[UIView transitionWithView:newView
duration:1
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{ // put the animation block here
}
completion:NULL];

Multiple modal views tap to dismiss

I set up a mechanism where a modal view controller can be dismissed by tapping the outside of the view. The set up is as follows:
- (void)viewDidAppear:(BOOL)animated
{
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapBehind:)];
[recognizer setNumberOfTapsRequired:1];
recognizer.cancelsTouchesInView = NO; //So the user can still interact with controls in the modal view
[self.view.window addGestureRecognizer:recognizer];
}
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
CGPoint location = [sender locationInView:nil]; //Passing nil gives us coordinates in the window
//Then we convert the tap's location into the local view's coordinate system, and test to see if it's in or outside. If outside, dismiss the view.
if (![self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil])
{
[self dismissModalViewControllerAnimated:YES];
NSLog(#"There are %d Gesture Recognizers",[self.view.window gestureRecognizers].count);
[self.view.window removeGestureRecognizer:sender];
}
}
}
This works amazing for dismissing a single modal view. Now suppose I have two modal views, one called from within the root view controller (View A) and then another modal called from within the first modal (View B)
Kind of like this:
Root View -> View A -> View B
When I tap to dismiss View B, all is well. However I get an EXC_BAD_ACCESS error when I try to dismiss View A. After turning on zombies, it seems that View B is still getting the message handleTapBehind: sent to it, even though it's been dismissed and out of memory after View B was closed.
My question is why is View B still being messaged? (handleTapBehind: make sure that gesture recognizer should have been removed from the associated window.) And how can I get it to be sent to View A after View B is already dismissed.
PS. The code above appears both inside the controller for View A and for View B, and it is identical.
EDIT
Here's how I am calling the modal view controller, this code is inside a view controller that is within the standard view hierarchy.
LBModalViewController *vc = [[LBModalViewController alloc] initWithNibName:#"LBModalViewController" bundle:nil];
[vc.myTableView setDataSource:vc];
[vc setDataArray:self.object.membersArray];
[vc setModalPresentationStyle:UIModalPresentationFormSheet];
[vc setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
[vc.view setClipsToBounds:NO];
[self presentViewController:vc animated:YES completion:nil];
// This is a hack to modify the size of the presented view controller
CGPoint modalOrigin = vc.view.superview.bounds.origin;
[[vc.view superview] setBounds:CGRectMake(modalOrigin.x, modalOrigin.y, 425, 351)];
[vc.view setBounds:CGRectMake(modalOrigin.x, modalOrigin.y, 425, 351)];
That's pretty much it, everything else is pretty standard.
[self dismissModalViewControllerAnimated:YES];
[self.view.window removeGestureRecognizer:sender];
Should be:
[self.view.window removeGestureRecognizer:sender];
[self dismissModalViewControllerAnimated:YES];
else you will get undefined results.

Focus outside UITableView to remove keyboard

I use a UITableView for a user/password login control. Right now it's the only control on the view, along with a login button. When i click inside, the text edit content becomes active, the keyboard pops up and I can type. however there is no way to stop the editing. I'd like to click outside on the white area of my UIView so that the focus is taken away from the text editors inside my UITableVIew and the keyboard becomes invisible again.
How do I do that?
You can add button which alpha channel is equil zero. There is behind your login and password field. Set action to this button in the following way:
-(void)hideKeyboard:(id)sender
{
[self.username resignFirstResponder];
[self.password resignFirstResponder];
}
In viewDidLoad make your view controller listen to keyboard notifications and create a tap recognizer which will receive all events outside of your tableView:
- (void)viewDidLoad
{
[super viewDidLoad];
...
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(keyboardWillShow:) name:
UIKeyboardWillShowNotification object:nil];
[nc addObserver:self selector:#selector(keyboardWillHide:) name:
UIKeyboardWillHideNotification object:nil];
tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(dismissKeyboard:)];
...
}
Then in the notification methods for the keyboard add and remove the gesture recognizer from your view.
//add gesture recognizer when keyboard appears
-(void)keyboardWillShow:(NSNotification *) note {
[self.view addGestureRecognizer:tapRecognizer];
}
//remove it when keyboard disappears
-(void)keyboardWillHide:(NSNotification *) note
{
[self.view removeGestureRecognizer:tapRecognizer];
}
In the action method of your gesture recognizer you resign all first responders to dismiss the keyboard:
-(IBAction)dismissKeyboard:(id)sender
{
//this removes ALL firstResponder from view
[self.view endEditing:TRUE];
}
Don't forget to end listening to the keyboard notifications at some point:
[[NSNotificationCenter defaultCenter] removeObserver:self];
Add a Single Tap GestureRecognizer and resign the keyboard in the called function
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(resignKeyBoard)];
[tap setNumberOfTapsRequired:1];
[self.view addGestureRecognizer:tap];

Resources