I have finding way to call pushViewController with scaling animation,
like facebook iPhone app main menu icon click animation.
(new viewController is popup from center, and it scales to original size.)
I searched several way to change animation of pushViewController.
First, I tried this:
viewController.view.transform = CGAffineTransformMakeScale(0.5f, 0.5f);
[UIView animateWithDuration:0.5f animations:^{
viewController.view.transform = CGAffineTransformMakeScale(1.0f, 1.0f);
}];
[self.navigationController pushViewController:viewController animated:NO];
but there is problem,
old viewController is disappeared when animation starts, there's only white background.
If I use CATransition to change animation,
I can show both old & new viewController both,
but there's no scaling animation, only move in, push in, fade in animations.
I want to show both new & old view controller like CATransition animations,
and need the way to implement my custom animation.
Here is my last suggestion, dirty way:
[self.view addSubview:viewController.view];
viewController.view.transform = CGAffineTransformMakeScale(0.5f, 0.5f);
[UIView animateWithDuration:0.5f
delay:0.0f
options:UIViewAnimationCurveEaseInOut
animations:^{
viewController.view.alpha = 1.0f;
viewController.view.transform = CGAffineTransformMakeScale(1.0f, 1.0f);
} completion:^(BOOL finished) {
[self.navigationController pushViewController:viewController animated:NO];
[viewController release];
}];
First, I added new viewController.view as subview, I can show animation with new & old view both.
When animation is ended, pushViewController later.
this way I can implement what I thought,
but I think it is dirty way, and there's remain problem:
Navigation bar items are not change immediately, It is changed after animation ends.
Is there any simple, clear way to implement this animation?
I think it is impossible to do that without change original implentation of pushViewController, should I do subclassing pushViewController?
Thanks to read, and your help.
In answer to the original question, I'm not sure if you consider this an improvement or not, but I think you might be able to include the navigator bar in the view that's being transformed (a) in IB, add a navigationBar to your view being transitioned to, (b) animate the hiding of the root navigation controller's navigation bar before you start your animation (so it slides off as your new view is sliding in), and (c) in your completion block of your animation, hide your new view's added navigation bar and unhide the root navigation controller's navigation bar. Maybe this renders something closer to what you intended, though probably different than you originally conceived. It's not perfect, though. The code, might look something like:
MyViewController *newController = [[MyViewController alloc] initWithNibName:#"MyView" bundle:nil];
newController.view.transform = CGAffineTransformMakeScale(0.1f, 0.1f);
[self.navigationController setNavigationBarHidden:YES animated:YES];
[self.view addSubview:newController.view];
[UIView animateWithDuration:0.5f
delay:0.0f
options:UIViewAnimationCurveEaseInOut
animations:^{
newController.view.alpha = 1.0f;
newController.view.transform = CGAffineTransformMakeScale(1.0f, 1.0f);
} completion:^(BOOL finished) {
[newController.view removeFromSuperview];
[self.navigationController pushViewController:newController animated:NO];
[newController.tempBar setHidden:YES];
[self.navigationController setNavigationBarHidden:NO animated:NO];
}];
It's adding a little more "dirt" to your "dirty way", but it might render the effect that you're looking for. You just want to make the temporary navigation bar in your new view to look as much like what the eventual root navigation controller's navigation bar will look like, and the user should be none the wiser. If this still doesn't quite achieve what you're looking for, you can just hide the root navigation controller's bar altogether, and always put your navigation on your views themselves, in which case the effect will be entirely seamless as you transition to your new view.
Alternatively, you could stay with your existing code and just set the title of the current view before you transition, that way it might look like the title change happened up front. But then you probably have to do stuff about resetting it when you return, setting the back button, etc., so it might not be worth it.
As an aside, and this is a little tangential to the original question (so I apologize in advance), but in response to the other suggestion that you shouldn't animate the view controller, but rather only views, I would like to voice a word of caution. Yes, technically that's right, but I'm nervous whether this will encourage people to adopt the bad practice of adding new views by (a) creating a new view controller; (b) animate the adding of that view controller's view to be a subview of the current view; but (c) not doing anything with that new view controller to add it to your view controller hierarchy (such as pushViewController or presentViewController).
If that's what was intended to the other answer, I have to say that I think this is a very bad idea. (If that's not what was intended, then my apologies, but I think it's easy for people to misconstrue the suggestion.) Bottom line, I have recently seen people do things like:
MyViewController *newController = [[MyViewController alloc] initWithNibName:#"MyView" bundle:nil];
// do some animation around adding the new view as a subview of your current view
// but neglect to ever invoke pushViewController or presentViewController
[self.view addSubview:newController.view];
This is not recommended for a couple of reasons. First, if it's an ARC project, your view controller will be discarded when newController falls out of scope. (There seem to be a spate of these sorts of questions being posted on SO resulting from exceptions being thrown as people transition to ARC.) Sure, you can fix this by making newController an ivar of your main controller so it won't fall out of scope, but be careful to remember to set it to nil when the main controller is eventually dealloc'ed or as you remove your subview, or else you can get a leak. And if you're in a non-ARC project, it's even easier to leak, so make sure to release the view controller when the subview is removed to prevent leaks.
Second, and probably more importantly, while the above code seems to work (or at least if you make the controller an ivar), but you end up with a disconnect between your hierarchy of your view controllers and that of your views. (In session 102 of WWDC 2011 about a different topic, view controller containment, it includes a lengthy, yet relevant, discussion of the importance of keeping view controller hierarchies and view hierarchies coordinated, e.g. rotation events may not be properly sent to your new view's controller because the controller isn't in the controller hierarchy, so iOS won't know to send them to your controller.) Bottom line, if you use another view controller's view to add as a subview of your current view, it is fragile, is susceptible to breaking on iOS upgrades, and things like rotation events will not be passed to your new view's view controller properly. If you do so, at least be aware that this is not good practice and make your own risk-assessment as to whether you want to do that. It's not necessary to bypass the proper view controller hierarchy, and I personally would like to dissuade people inclined to do so.
Bottom line, if you're transitioning to a view of a different view controller, you really want to stick to pushViewController or presentViewController, and do your animation around the new controller's view (and usually it's nowhere near as complicated as this animation ... generally it's incredibly simple and I've done all sorts of fade and flip animations with much greater ease ... it's the use of the transform that is making this complicated).
Alternative, if you want to animate a new UIView subview and not deal with view controllers at all, just make sure it's not another view controller's view, but rather, for example, a view you create programmatically that uses the same view controller as your originating view. And if you decide to use another view controller's view as a subview of your existing controller, just do so with the knowledge that it's a little fragile and some events may not be transmitted to your new view controller as you expect and it might not be as "future proof" as you may want.
I don't think that animation is done with a view controller. You can just keep the existing view controller. Create a new UIView that is supposed to zoom in. Add that new UIView to the existing view controller and apply animation to it.
Related
I've been struggling to find an answer to this question. I build a stack of modals via:
[[[NavA viewControllers] objectAtIndex:0] presentViewController:NavB animated:YES completion:NULL];
[[[NavB viewControllers] objectAtIndex:0] presentViewController:NavC animated:YES completion:NULL];
When I want to dismiss the NavA and NavB modals simultaneously I call
[[[NavA viewControllers] objectAtIndex:0] dismissViewControllerAnimated:YES completion:NULL];
This works fine except there's a brief flash where you can see NavB as the full stack is dismissed.
I stepped through the debugger and it looks like before the animation begins NavC disappears instantly and NavB dismisses with animation.
Is there any way to avoid this visual artifact and have the whole stack dismiss smoothly with NavC visible for the full duration of the animation?
Edit: To clarify, I'm presenting UINavigationController rather than UIViewController because this flow is for user login and has multiple possible branches that can lead back either to the current stage e.g. NavC (LoginPage), NavB (LandingPage with login and signup buttons) or all the way back to the root, NavA (main page of the application). In the iOS documentation they present a similar design pattern with the camera where each stage presents a UINavigationController with multiple possible view controllers https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/ModalViewControllers/ModalViewControllers.html
Actually there's no way to do it by just using dismissViewControllerAnimated:completion: method no matter where you put it or how you call it (at least I couldn't, if someone knows a way - we all want to know).
HOWEVER, there's a hack you can use to achieve your desired outcome (this code should be called from "B" ViewController):
// Snapshot of "C" ViewController
UIGraphicsBeginImageContextWithOptions([UIScreen mainScreen].bounds.size, YES, 0);
UIView *snapshot = [self.presentedViewController.view snapshotViewAfterScreenUpdates:NO];
UIGraphicsEndImageContext();
// Cover the entire view of "B" (and hide navigation bar)
[self.view addSubview:snapshot];
self.navigationController.navigationBarHidden = YES;
// Dismiss "C" without animation
[self.presentedViewController dismissViewControllerAnimated:NO completion:^{
// Dismiss "B" with animation
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}];
If you are using a Storyboard then this should be achievable using Unwind Segues. Mike Woelmer has a good set of articles about this. Basically you provide information to the Storyboard about how a view can unwind through several different views to get a to a view that's already on the stack.
However, I'm a bit confused in the same way that Jeffery Thomas is in the comments: why are you presenting a navigation controller with another navigation controller? I can understand that you might want the navigation bar to look different on different views, but you can customise that when the view is due to appear. You should think a bit about the content of the views in NavB and NavC and ask yourself whether they are supposed to be presented as modal views or whether they would be better off as part of a single navigation stack. By presenting each Navigation Controller modally you're ending up with multiple navigation stacks, not a single stack with multiple view controllers. Even if just NavB and NavC were part of the same stack it would probably remove the visual glitch you're seeing.
If you did use a single navigation controller then you can get back to a previous view controller in the navigation stack by using the method -popToViewController:animated: on UINavigationController.
If you decide that presenting NavB and NavC modally as you are currently doing is the right thing to do then you are likely to get into trouble because when you ask NavA to dismiss its view controller it will try to dismiss NavB, which to it means setting up a transition between the NavB's view and NavA's view. That's why you're seeing that transition, and not the one you want (which is between NavC's view and NavA's). One way which might work (and sounds a bit weird) is to try to present NavA from NavC, then override the transition to make it look like you're popping NavC off the stack. Once you're there you can clean things up by removing any strong references to NavB and NavC. This article from Ash Furrow will get you most of the way.
You can fake the animation to look exactly as you wish:
pop/dismiss B and C without animation
push/present C without animation
pop/dismiss C using whatever animation you wish
I have a setup that is very similar to a UITabBarController but for various reasons I need to create my own custom container root controller.
I would like to create a custom transition between my two view controllers as specified in the picture (VC 1 & VC 2).
Is it correct to add my two view controllers as children to my master controller and do a view animation on the container views that live within the root view controller? That would mean that both of my view controllers are instantiated at once.
Or do I go about doing something like having VC 1 live as a child view controller on my root view controller while later instantiating VC 2 in my code when the transition takes place. This of course would mean that I would have VC 2 live in my storyboard but not connected to the root view controller and be instantiated through the Storyboard ID. And I would obviously be using the custom transition protocols that we received in iOS 7.
Or is there some other option?
Since they're both contained within one ViewController, you probably won't transition between them using present and dismiss.
I recently had a similar scenario where I finally went with using my own custom transitions from the ContainerViewController.
There are some things to think about when using ViewController containment. You should checkout this link on developer.apple, containing some good practices and examples on View Controller containment and how to animate in between ChildViewControllers.
When it comes to allocation, I'd say it's up to you. Unless these ViewControllers take a lot of memory, I'd probably go with instantiating the first one to be shown, and doing a lazy initialisation (initialise when needed) on the second one, and then retain them both in memory. After a transition has been made, make sure to remove the "unused" ViewController's view from the container and it should be all good.
Here's a simple example to fade from the firstVC's view to the secondVC's view:
[self addChildViewController:self.secondViewController];
[self.view insertSubview:self.secondViewController.view belowSubview:self.firstViewController.view];
[UIView animateWithDuration:0.4 animations:^{
self.firstViewController.view.alpha = 0;
} completion:^(BOOL finished) {
[self.firstViewController willMoveToParentViewController:nil];
[self.firstViewController.view removeFromSuperview];
[self.firstViewController removeFromParentViewController];
[self.secondViewController didMoveToParentViewController:self];
}];
I've been toying around with an idea of a UINavigationController that has an image as background and every view controller on the stack would have transparent background so the background image of the UINavigationController is visible across all of them.
I tried implementing this the most obvious way (have root view controller with fullscreen image and view controllers with transparent background, but this results in awkward issues with animation).
Digging around I found HotelTonight app managed to implement this. See a recording of their user interface. I made: https://www.youtube.com/watch?v=qvynwhCj5oM&list=UUKnxsyMsRRRJs_Yw9GZVOFw
Does anybody have some suggestion on a right path implementing this?
It's probably better to subclass UIViewController to have a particular setup for its background and navigation controller style. For example, in the viewDidLoad method of your UIViewController subclass, set its background to a certain image. You can also use this to style your navigation bar (certain colors for the barButtonItems, etc). Then each new view controller you create is a subclass of your skinned view controller.
If your background is an image, you will still get the weird animation effects when a new view controller slides in over the top of the old one; the background will slide in as well. Depending on how custom you want to make your transition animations, there are things you can do to play around with the views during the transition. UIPresentationController might present some solutions for making a more custom animated transition.
Edit: To emulate the Hotel now app, I made a UINavigationController with an image as a background, and controllers on top with clear backgrounds. On a push, I animate the alpha of the first controller to 0 while the segue is happening. On returning, I set the alpha back to 1.
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.view.alpha = 1;
}
-(void)didClickButton:(id)sender {
[UIView animateWithDuration:.25 animations:^{
self.view.alpha = 0;
}];
// the transition itself is a push segue defined in the storyboard
}
I believe that the Hotel App's background works well with this method since it's very dark. Not sure if it would be very effective for brighter apps or apps with a lot of content in the controller being covered.
I am new to ios development.
Setup
The view hierarchy is as follows:
MyViewController has the following
myView ( UIView)
-> Navigation Bar
-> UiCollectionView
-> UIButton
What is going on ??
RootViewContoller presents myViewController
(things work till this point)
What i want to do after that??
On clicking "UIButton" :- reload the "myView" with a different text in the navigation bar and different content in the "UICollectionView".
Animation requirements : Reload/Refresh of the view should happen with transition effects of the view for eg: flip from left, curl up etc.
->I tried [myView setNeedsDisplay] to see if the view reloads without animation - doesn't work
-(IBAction) onReload {
[self.myView setNeedsDisplay];
}
-> I also tried
[UIView transitionWithView:myView ....
although i don't know what to put in animation block which would trigger the refresh of view.
I would appreciate any help. Thanks
Create a second instance of your view controller, properly setup as far as its title and UICollectionView content are concerned, then when handling your button action, do:
[self.navigationController pushViewController:newViewController animated:NO];
This is the basic way you handle a navigation hierarchy under iOS: you create new controllers and push/pop them to/from the navigation controller.
If you are not using a navigation controller, rather you are trying to implement the same functionality as a navigation controller would handle on your own, I strongly suggest you to use a navigation controller instead... :-)
Hope this helps.
I've created my own split view controller for the iPad. My main purpose in creating my own is to allow the user to open/close the left pain at will. The problem I'm having is I can't find a way to get the view controllers in my right pain to animate the changes. I animate the views in my split view controller, but all other sub view controllers in the right pane don't animate their views. I'm not switching views, so I can't use any of those methods and I'm not changing orientations, so those methods are out (although, I did try them anyway but it didn't work). Does anyone know of a way to do this?
Currently, my code to initiate the animations is:
- (void) setShowLeftPane:(BOOL)showLeftPane{
if (_showLeftPane != showLeftPane){
_showLeftPane = showLeftPane;
//Alert viewcontrollers and perform animation
if (showLeftPane) [_leftController viewWillAppear:YES];
else [_leftController viewWillDisappear:YES];
[UIView animateWithDuration:.25 animations:^{
[self layoutViews];
[_rightController viewWillLayoutSubviews];
} completion:^(BOOL fin){
[_rightController viewDidLayoutSubviews];
if (_showLeftPane) [_leftController viewDidAppear:YES];
else [_leftController viewDidDisappear:YES];
}];
//Switch out the appropriate pane button view
[_showLeftPane ? _openLeftPane : _closeLeftPane removeFromSuperview];
[self.view addSubview:_showLeftPane ? _closeLeftPane : _openLeftPane];
}
}
Currently my right pane view controller is a UINavigationController, so I'm dealing with a stock class. I've considered adding a category method viewWillAnimateSubviewLayoutWithDuration:(NSTimeInterval)interval to UINavigationController that will cycle through it's view controllers testing for/sending that same method to it's view controllers so that they can perform their animations, but that's not quite ideal especially if there's a way to use stock methods.
I'm considering writing my own NavigationController but that's a lot of work I don't want to do if I don't have to.
For those that care, I ended up writing my own Navigation Container Controller. It was a lot of work, but it ended up being ideal for a few reasons. First, creating the custom container controller gave me a much more intimate understanding of how view controllers work, which has been helpful. Second, it gave me much more control over the how the 'navigation controller' works, allowing me to easily implement custom transitions and custom pop/push methods, including a 'replace' (pop, then push) method that was sorely needed in my application.