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