Multiple modal views tap to dismiss - ios

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.

Related

iOS custom UIViewController container - view lifecycle and dealloc

I have written a custom view controller container that can replace the existing view controller (VC1) with a new view controller(VC2) i.e. a replace type segue rather than pop/push.
Everything worked fine until testing with iOS 8. It seems a second party I'm using causes a crash if the view controller is replaced before the previous view controller is dealloc
i.e. if [VC2 viewDidLoad] called before [VC1 dealloc].
It is my understanding that I have no control over the dealloc so this kind of behaviour is out of my control. However, I want to make sure that I am not causing this behaviour in the way I'm controlling my child view controllers.
The following is pseudo code for my view controller container:
- (void)replaceStackWithNewController:(UIViewController*)newVC {
for(UIViewController *vc in [self viewControllers]) {
[vc willMoveToParentViewController:nil];
if([vc isViewLoaded]
[[vc view] removeFromSuperview];
}
[vc removeFromParentViewController];
}
//self.viewControllers is my navigation stack
self.viewControllers = nil;
self.childVC = nil;
if(newVC != nil) {
self.childVC = newVC;
}
[self addChildViewController:newVC];
//this adds the view to a pre existing wrapper
[self addView:newVC.view ToWrapper:wrapper];
//add controller to stack
[self.viewControllers addObject:newVC];
[newVC didMoveToParentViewController:self];
}

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];

iOS - how to dismiss keyboard?

In my app, I press a button and it pulls up a modal presentation sheet (for iPad). Within this modal view I have a scrollview within my main view, and 1 text field within my scroll view.
view controller
view
scrollview
text field
Nothing I have tried resigns the keyboard and I don't know why. The only thing that happens is the blinking cursor in the textfield goes away. My class is the delegate for the scrollview and text fields. Here is what I have tried:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
[self.titleTextField resignFirstResponder];
[self.titleTextField endEditing:YES];
[self.view endEditing:YES];
[self.view resignFirstResponder];
[self.scrollView endEditing:YES];
[self.scrollView resignFirstResponder];
}
The method does get called, but the keyboard doesn't go away. Can anyone help me or at least tell me why?
Here is how I present this modalpresentation view:
(it comes from a tableviewcontroller)
didSelectRowAtIndexPath
EditVideo *targetController = [self.storyboard instantiateViewControllerWithIdentifier:#"editVideo"];
targetController.delegate = self;
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:targetController];
navigationController.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentViewController:navigationController animated:YES completion:nil];
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
On the iPad for any any non-fullscreen presented ViewController, you must implement -(BOOL)disablesAutomaticKeyboardDismissal to return NO to dismiss the keyboard.
- (BOOL)disablesAutomaticKeyboardDismissal
{
return NO;
}
Once that is implemented, you can call [self.view endEditing:YES];.
Edit:
The other common cause of this problem is returning NO from - (BOOL)textFieldShouldEndEditing:(UITextField *)textField. Implement the method in the UITextFieldDelegate and have it return YES unconditionally to prove that it is not a factor.

iOS app crashes at tap gesture event

I have a view targeted for iPad that I present modally with modalPresentationStyle property set to UIModalPresentationFormSheet, by pushing firstly its view controller into a navigation controller:
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:myViewController];
navController.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
navController.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentViewController:navController animated:YES completion:nil];
Then, in the presented view controller, I want to detect tap gestures outside itself (as I said, I present it as a form sheet), so I have set a tap gesture this way:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
self.tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(handleTap:)];
self.tapGestureRecognizer.numberOfTouchesRequired = 1;
self.tapGestureRecognizer.numberOfTapsRequired = 1;
self.tapGestureRecognizer.cancelsTouchesInView = NO;
[self.view.window addGestureRecognizer:self.tapGestureRecognizer];
}
- (void)handleTap:(UITapGestureRecognizer*)sender
{
if (sender.state == UIGestureRecognizerStateEnded) {
CGPoint location = [sender locationInView:nil];
if (![self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil]) {
[self.view.window removeGestureRecognizer:sender];
[self dismissModalViewControllerAnimated:YES];
}
}
}
The navigation controller I'm presenting modally, and whose root view controller is the one setting this gesture recognizer, displays more views in hierarchy. When only the root view controller is pushed onto the navigation controller stack and I use the tap gesture outside it, it is correctly dismissed and I'm able to present modally this root view controller again. When I navigate from the root view controller and I push one more view controller onto the navigation stack, the tap gesture still works, but then the app crashes when I try to show the form sheet again.
How should I handle this gesture and the behavior I want having a navigation hierarchy?
Thanks in advance
I think your problem is removing the gesture recognizer with self.view.window -- that will be nil when another controller is pushed on the stack, since only the view on screen has a non-nil window property. Try replacing that with [UIApplication sharedApplication].delegate.window and see if that fixes the problem.
One of the possible cause of crash I've encountered is forgetting to declare your view controller as a childViewController to its parent controller.

swipe to new nib

I just want to be able to swipe to a new nib, but am getting stuck. My code builds properly with no errors or warnings, but nothing happens.
Here is my code
- (IBAction) handleSwipeGesture:(UIGestureRecognizer *) sener {
NSLog(#"swipe left");
if(UISwipeGestureRecognizerDirectionLeft) {
SecondDetailViewController *tempController = [[SecondDetailViewController alloc]
initWithNibName:#"SecondDetailView"
bundle:nil];
newController = [tempController retain];
[tempController release];
}
}
The system sees that I am swiping (it gets logged) but it doesn't go to the new nib.
I'm not really married to my code, so if I need to completely rewrite this I'm OK.
Assuming you're handling swipe gesture inside a view controller (and you are using navigation controller), you should do something like this:
- (IBAction)handleSwipeGesture:(UISwipeGestureRecognizer *)sender {
if(sender.direction == UISwipeGestureRecognizerDirectionLeft) {
NSLog(#"swipe left");
SecondDetailViewController *tempController = [[SecondDetailViewController alloc]
initWithNibName:#"SecondDetailView" bundle:nil];
[self.navigationController pushViewController:tempController animated:YES];
[tempController release];
}
}
This line pushes tempController on top of the navigation controller:
[self.navigationController pushViewController:tempController animated:YES];
if you want to present tempController modally, you should call this instead:
[self presentModalViewController:tempController animated:YES];
You are not doing anything with the newController object after you create it. You need to either push it onto your nav controller, present it modally, or grab the view and put it on the screen.

Resources