How can I animate the transition between two modal view controllers? - ios

Edited original post for clarity
I'd like to be able to control the animation between two modal view controllers. Specifically, I'd like to have the first modal slide down and the second modal fade in behind it. I just watched the WWDC videos on custom animation transitions and I think that is probably what I want. My thought is that during the sliding down of modal1, I can fade in modal2 (or an image of modal2) and then just present modal2 normally once the animation is finished. I'm not sure if this is the right way to do it so I'm open to ideas.
This is how I'm dismissing/presenting:
_modal1.modalPresentationStyle = UIModalPresentationCustom;
[_modal1 setTransitioningDelegate:self];
[_modal1 dismissViewControllerAnimated:YES completion:^{
[self presentViewController:_modal2 animated:NO completion:nil];
}];
And here's my delegate method:
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
return [CustomDismissAnimation new];
}
And my attempt at a transition:
#implementation CustomDismissAnimation
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
UIView *containerView = [transitionContext containerView];
UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
CGRect containerFrame = containerView.frame;
CGRect fromViewFinalFrame = CGRectMake(0,
containerFrame.size.height,
fromView.frame.size.width,
fromView.frame.size.height);
UIViewController *test = [UIViewController new];
[test setBackgroundColor:[UIColor greenColor]];
test = containerFrame;
[containerView addSubview:test];
[containerView bringSubviewToFront: test];
[UIView animateWithDuration:[self transitionDuration:transitionContext]
animations:^{
[fromView setFrame:fromViewFinalFrame];
}
completion:^(BOOL finished){
[test removeFromSuperview];
[transitionContext completeTransition:YES];
}];
}
#end
My expectation would be that this green test view would appear behind modal1 as it slid down the screen but that doesn't seem to be the case. It doesn't show up at all. Any ideas?
Thanks!

One simple workaround of that you can't have 2 modal VCs shown , is that create only one modal with 2 views the front is the old and the back is the new (If you want to separate VCs make newVC as a child behind the presented old), and manage the hide/show between them also you can do the animation to the view directly

So you are saying A presents B, then dismisses B, then presents C, and yet A should not visible while dismissing B and presenting C.
One obvious solution would be to let B present C! When you take C down you could unwind all the way thru B to A in one move.
If you insist upon this hierarchy — that is, if A is present B and then C — B and C cannot occupy the space at the same time, because one view controller (A) cannot present two VCs at once. So something is going to appear behind B as it vanishes and then behind C as it appears. However, you are free to say what that is; you could, for example, interpose a "dimming view" in front of A.

Related

Creating a new navigation stack with a flip transition instead of pushing another controller

I had this code to make a flip transition between UITabControllers:
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"OtherSb" bundle:nil];
PrimaryTabBarController *tabBarController = [sb instantiateInitialViewController];
[UIView transitionWithView:[APP_DELEGATE window]
duration:0.8
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{
[[APP_DELEGATE window] setRootViewController:tabBarController];
[[APP_DELEGATE window] makeKeyAndVisible];
}
completion:nil];
However, strangely, during the flip transition, the tab bar briefly flashes from the bottom to the top of the screen. I was able to make that stop by doing the following:
PrimaryTabBarController *tabBarController = [sb instantiateInitialViewController];
tabBarController.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentViewController:tabBarController animated:YES completion:nil];
The problem with this however is that I'm pushing another view controller onto the stack which can easily run through the memory. How can I create a new navigation stack without having the tab bar mess up the animation?
rootViewController is not very animatable property as you can imagine. makeKeyAndVisible is not animatable either and you should probably do that before you run any animations.
This API itself is very old (iOS 4.0) and I personally consider it more of a legacy of UIKit and I haven't seen it being used since iOS 6 when flipping views was still a thing.
Custom transitions introduced in iOS 7 is a very comfortable way of doing any kind of crazy animations however as you noticed, it creates a modal hierarchy that you don't always need.
All of these API were designed to work with sibling views within container. This is something mentioned in documentation and its samples. And it seems like that UIWindow is not suitable as global animation container.
Sample code from documentation:
[UIView transitionWithView:containerView
duration:0.2
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{ [fromView removeFromSuperview]; [containerView addSubview:toView]; }
completion:NULL];
I suggest that you follow the same logic as custom transitions and first setup dummy root controller that will serve you as a container for animation.
Then you add your views or entire view controllers inside of it and run animation between sibling views using
+ transitionFromView:toView:duration:options:completion:`
or
- transitionFromViewController:toViewController:duration:options:animations:completion:
or
+ transitionWithView:duration:options:animations:completion:
In addition to that, there is a useful flag UIViewAnimationOptionShowHideTransitionViews that will automatically hide the flipped view to avoid it to flicker or re-appear after animation.
When animation is over, you can swap the entire root controller in one call, that should be unnoticed to user.
This API has some quirks too, for example if by any chance you use it when the app is not on screen or you run it on a window that is not currently visible, then it will simply swallow the call. I used to have a check like
if(fromViewController.view.window) {
/* run animations */
} else {
/* swap controllers without animations */
}
I made a sample project to demonstrate how to use temporary container view for transition
https://github.com/pronebird/FlipRootController
Sample category on UIWindow:
#implementation UIWindow (Transitions)
- (void)transitionToRootController:(UIViewController *)newRootController animationOptions:(UIViewAnimationOptions)options {
// get references to controllers
UIViewController *fromVC = self.rootViewController;
UIViewController *toVC = newRootController;
// setup transition view
UIView *transitionView = [[UIView alloc] initWithFrame:self.bounds];
// add subviews into transition view
[transitionView addSubview:toVC.view];
[transitionView addSubview:fromVC.view];
// add transition view into window
[self addSubview:transitionView];
// flush any outstanding animations
// UIButton may cancel transition if this method is called from touchUpInside, etc..
[CATransaction flush];
[UIView transitionFromView:fromVC.view
toView:toVC.view
duration:0.5
options:options
completion:^(BOOL finished) {
// set new root controller after animation
self.rootViewController = toVC;
// move VC's view out of transition view
[self addSubview:toVC.view];
// remove transition view
[transitionView removeFromSuperview];
}];
}
#end

Objective C How to animate Subview with click in Button

I have a problem :D.
I'm currently using a SplitViewController. At the start I want to hide the TableViewController in the MasterView. So i decided to add a SubView in the Storyboard into the tableView, where a little label will be shown.
So after I make my choice in the DetailView and click the Button I want to hide the subView and want to show the tableView. That all with animations.
So here is my Code:
In the TestTableViewController.m
-(void)AnimateView{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.1];
[UIView setAnimationCurve:UIViewAnimationCurveLinear];
fixedView.frame=CGRectMake(-500, 0,0,0);
[UIView commitAnimations];
NSLog(#"test");
And in the TestDetailViewController.m
- (IBAction)attractionButton:(UIButton *)sender
{
TestTableViewController *testTableViewController = [[TestTableViewController alloc]init];
[testTableViewController AnimateView];
}
I get the NSLog "test" but the Animation is not working.
fixedView is my Subview and I drag it to the header file so it's an IBOutlet.
Thank you so far.
So you seem to instantiate a new ViewController and trying to perform this animation straight away:
TestTableViewController *testTableViewController = [[TestTableViewController alloc]init];
[testTableViewController AnimateView];
The problem is that you don't even present that view controller (its view is not on screen anywhere) so it's normal you don't see any animation.
From my understanding you already have another instance of this view controller presented on screen (am I right?) - so that is the one that you need to use. Pass its pointer to TestDetailViewController in some way (e.g. a property) and then use that object to call your animation method.
PS. I would recommend naming all your methods starting with a lower case letter ;)
The problem is, you are not referencing the same master view controller in your details view controller.
You are creating another one in your IBAction.
In order to get a hold of your (the "original" one) master view controller, do this in your detail view controller:
Create a ivar:
TestTableViewController *masterViewController;
And then to reference it:
masterViewController = (TestTableViewController*)[[[self.splitViewController.viewControllers lastObject] viewControllers] objectAtIndex:0];
For example:
- (IBAction)attractionButton:(UIButton *)sender
{
masterViewController = (TestTableViewController*)[[[self.splitViewController.viewControllers lastObject] viewControllers] objectAtIndex:0];
[masterViewController AnimateView];
}
And finally in your AnimateView method, make change to the following:
[UIView animateWithDuration:0.1f animations:^
{
fixedView.frame = CGRectMake(-500, 0, 0, 0);
}
completion:nil];
Hope this helps.
What you could try is this:
[UIView animateWithDuration:0.15f animations:^{
fixedView.frame=CGRectMake(-500, 0,0,0);
}completion:^(BOOL finished){
}];
Are you sure you can call [UIView beginAnimations: ....] without a first argument?

iOS: animation between view controllers

I can't find this anywhere. So, if you possess the info about it, please give me a link.
I have one view controller, I made a menu for a simple game. There are some buttons on it.
I have another view controller and there some buttons too.
The question is:
"How I can do an animation of this buttons (hiding off the screen) after I choose one button that triggers a custom segue (without any animation) to another View Controller, which will run it's button animation(coming to the screen from a border of the screen)?"
I made this like this:
1) Theory: I make a IBAction for a menu button, then in this IBAction I call an animation method, which call a performSegueMethod:. After this in new VC in viewWillAppear method call a animation method (that almost equal method from source VC). All this works, but this don't look smooth. The problem with this animation occurs when destination VC replace source VC. There is some split second, when all looks static, and only after this animation starts.
I don't know how to remove this destination VC lag. May be I must load a destination view before a segue? I tried to do this, but may be a made something wrong, or it's just don't help me.
2) Practice:
firstViewController.m:
- (IBAction)newGameSegueButton {
[self menuSlideInDirection:#"CenterToLeft" performingSegueWithIdentifier:#"mode"];
}
-(void)menuSlideInDirection:(NSString *)direction performingSegueWithIdentifier:(NSString *)segueIdentifier
{
[UIView animateWithDuration:0.4 animations:^{
CGPoint newGameButtonCenter;
newGameButtonCenter.x = directionIndex * 160;
newGameButtonCenter.y = self.gameButtonSlide.center.y;
self.gameButtonSlide.center = newGameButtonCenter;
[UIView animateWithDuration:0.3 delay:0.1 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
//some animation too
} completion:nil];
[UIView animateWithDuration:0.2 delay:0.2 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
//animation
} completion:nil];
[UIView animateWithDuration:0.1 delay:0.3 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
//some animation too
} completion:^(BOOL finished){
if(segueIdentifier){
[self performSegueWithIdentifier:segueIdentifier sender:self];
}
}];
}];
}
Okay, then custom segue code is pretty simple:
-(void)perform
{
UIViewController *src = self.sourceViewController;
UIViewController *dst = self.destinationViewController;
[src.navigationController pushViewController:dst animated:NO];
}
And my secondViewController.m:
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self menuSlideInDirection:#"RightToCenter" performingSegueWithIdentifier:nil];
}
menuSlideInDirection:#"RightToCenter" performingSegueWithIdentifier:nil in secondViewController identical to method with same name in firstView.
I'm looking for smooth animation of particular objects of destination view controller right after a segue.
May be a doing all wrong and there a another way to do this? (I only think of adding all view and all controls of destination VC to source VC and remove "destination VC" at all).
Hope, somebody can help me with this.
Try doing all first view controller's animations in perform method.
As for the second view controller's animations, I don't think there is any other 'good' way than doing it in viewWillAppear (although I would prefer viewDidAppear) since the outlets of the destination view controller are not set while performing the segue(they will be nil). In other words, you do not have a way to access your buttons, let alone animate them.
A hackish way would be to call [segue.destinationViewController view] before perform so that the destination view controller's view hierarchy is loaded and the outlets are set. Then perhaps, you may animate buttons in the destination view controller in perform before pushing it onto navigation stack.
For my question, I choose the way of putting two views under the same VC. Animation is smooth and it's look much better than using perform / viewWillAppear method.

Moving view of ViewController after modalViewController dismissed

I have two ViewControllers: A and B. A is a delegate of B. I am presenting B from A and when I dismiss it, I am trying to move the views of A. I have methods set up in the delegate to listen to when B is dismissed to try to move the views. This is what I am using now when B is dismissed, but it doesn't work. How can I get this to work?
[self dismissViewControllerAnimated:YES completion:^{
NSLog(#"view dismissed");
[self moveViews:self];
}];
- (IBAction)moveViews:(id)sender
{
[UIView animateWithDuration:0.5
animations:^{
self.view.center = CGPointMake(160, 250);
//self.tableView.center = CGPointMake(160, -100);
self.addView.center = CGPointMake(160, 240);
}
completion:^(BOOL finished){}];
}
looking at your moveViews method, you aren't moving the views from one view controller to the other. You are moving the position of the view. The problem is that the view controller being dismissed will no longer be a part of the screen, so it wont be visible. My recommendation would be to implement iOS 5 UIViewController containment and add the dismissed view controller as a child view controller of the other view controller. The reason for this is because the information contained on the modal view controller (the controls, target for actions and the model) is a part of the modally presented view controller. Check the documentation for the UIViewController class and the containment mentioned here:
http://developer.apple.com/library/ios/#documentation/uikit/reference/UIViewController_Class/Reference/Reference.html
I think the problem is, that you call here [self moveViews:self]; the method on the view B.
Have a id (for example named sender ) property in B.
When you make the transition set the sender property in B to A.
Call
[self dismissViewControllerAnimated:YES completion:^{
NSLog(#"view dismissed");
[self.sender performSelector:#selector(moveViews)];
}];
then B will be dismissed and calls method moveViews on A.
In A you have a method
- (void)moveViews
{[UIView animateWithDuration:0.5
animations:^{
self.view.center = CGPointMake(160, 250);
}
];
}
should work, have fun

How to code a custom segue that "slides" between View Controllers?

I built a app with storyboard, with an initial view controller, connected to many subsequent view controllers, connected sequentially, using cross dissolve segue. These work one swipes. All works fine. However, instead of being forced to use the basic segues, I want to have a custom segue that will slide the view controller content on and off the screen, much like the push for navigationControllers, but, being enabled to go left and right depending if one is going forward or backwards in the app.
I have set the segue to custom, and have created and saved a MySegue.h file. HOWEVER, I don't know how to code a custom segue that will slide one viewcontroller off the screen as the other slides on and back and forth as I move between view controllers.
Can anyone please provide me with come coding (it should be easy!) for a custom segue to move from one view controller to the next and back by sliding the screen on and off so I don't have to use the basic cross dissolve or standard flips offered in Xcode 4.2? I would be most grateful.
I was running into the same issue here, but I was using swipe gestures to go from one view controller to the next. Using the storyboard itself to set up segues was working fine, but when I needed a swipe to "go back" to the previous view controller, the animation went from right-to-left, instead of left-to-right. To fix that issue I did the following:
Embedded a Navigation Controller in the root view controller.
Used segues to push new view controllers.
When I wanted to "go back" to the previous view controller, I did not add a segue to the storyboard to push the previous view controller. Instead, I called the Navigation Controller's popViewControllerAnimated method whenever a back swipe occurred.
To create a custom segue that slides view controllers first create a class that extends UIStoryboardSegue, then override the perform selector. I just made something like this and it should work for you too. Here's my code:
#define kAnimationDuration 0.5
#import "CustomSlideSegue.h"
#implementation CustomSlideSegue
- (void)perform
{
UIViewController *sourceViewController = (UIViewController *) self.sourceViewController;
UIViewController *destinationViewController = (UIViewController *) self.destinationViewController;
[sourceViewController.view addSubview:destinationViewController.view];
[destinationViewController.view setFrame:sourceViewController.view.window.frame];
[destinationViewController.view setBounds:sourceViewController.view.bounds];
CGSize screenSize = [[UIScreen mainScreen] bounds].size;
if ( !self.slideLeft ) {
[UIView animateWithDuration:kAnimationDuration
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
[destinationViewController.view setCenter:CGPointMake(screenSize.height + screenSize.height/2, screenSize.height/2 - 138)];
[destinationViewController.view setCenter:CGPointMake(screenSize.width/2 + 127, screenSize.height/2 - 138)];
}
completion:^(BOOL finished){
[destinationViewController.view removeFromSuperview];
[sourceViewController presentViewController:destinationViewController animated:NO completion:nil];
}];
} else {
[UIView animateWithDuration:kAnimationDuration
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
[destinationViewController.view setCenter:CGPointMake(-1*screenSize.height/2, screenSize.height/2 - 138)];
[destinationViewController.view setCenter:CGPointMake(screenSize.width/2 + 127, screenSize.height/2 - 138)];
}
completion:^(BOOL finished){
[destinationViewController.view removeFromSuperview];
[sourceViewController presentViewController:destinationViewController animated:NO completion:nil];
}];
}
}
Then when you want to perform the segue use this code:
UIViewController *destination = [self.storyboard instantiateViewControllerWithIdentifier:#"some identifier"];
CustomZoomSegue *segue = [[CustomZoomSegue alloc] initWithIdentifier:#"segue vc identifier" source:self destination:destination];
[self prepareForSegue:segue sender:self];
[segue perform];
The CGPointMake calls are custom to my code (I used landscape orientation) but you should be able to change it to fit your needs. If you need further clarification or have any question let me know and I will try to answer.
NOTE: I use storyboards with no segue lines between the view controllers.
Why not just use a UINavigationController? This seems to be exactly what they're designed for.

Resources