I'm looking to perform a segue to replace the window's root view controller by another view controller using a curl up animation.
The idea is that I have a SplashViewController
being displayed for a couple of seconds before transitioning (performSegueWithIdentifier:
) to the next one, LoginViewController
, using the curl up animation.
I've created a custom UIStoryboardSegue
class called AnimatedSegue
. Here is the code of the overridden perform
method:
- (void)perform
{
UIViewController *source = self.sourceViewController;
UIViewController *destination = self.destinationViewController;
UIWindow *window = source.view.window;
[UIView transitionFromView:source.view
toView:destination.view
duration:1.0
options:UIViewAnimationOptionTransitionCurlUp
completion:^(BOOL finished) {
[window setRootViewController:destination];
}];
}
It works fine except that in iOS 6 (apparently not in iOS 5) the viewWillAppear:
method is being called twice on the destination
view controller.
It seems that it's called a first time during the transition and second time when it executes [window setRootViewController:destination];
Note that I don't want to use a navigation controller. The SplashViewController
gets deallocated (as expected) once the transition is over.
Any ideas on how to fix my problem?
Answering to my own question in case it can help someone else...
I ended up using Core Animation's CATransition
class to create the segue's animation instead of the UIView
's animation methods.
Here's how my new perform
method looks like:
- (void)perform
{
UIViewController *source = self.sourceViewController;
UIWindow *window = source.view.window;
CATransition *transition = [CATransition animation];
[transition setDuration:1.0];
[transition setDelegate:self];
[transition setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
[transition setType:@"pageCurl"];
[transition setSubtype:kCATransitionFromBottom];
[window.layer addAnimation:transition forKey:kCATransition];
[window setRootViewController:self.destinationViewController];
}
I would probably do this in a different way, using 2 views and one controller, rather than 2 controllers with a custom segue. In your storyboard, just have a blank view for the controller, and add two xib files (of the view type) with the splash view and a main view. The splash view would be added as a subview of the controller's view in viewDidLoad, and then switched out using the same method you did:
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.splashView = [[NSBundle mainBundle] loadNibNamed:@"SplashView" owner:self options:nil].lastObject;
[self.view addSubview:self.splashView];
[self performSelector:@selector(removeSplash) withObject:nil afterDelay:2];
}
-(void)removeSplash {
self.mainView = [[NSBundle mainBundle] loadNibNamed:@"MainView" owner:self options:nil].lastObject;
[UIView transitionFromView: self.splashView toView:self.mainView duration:.6 options:UIViewAnimationOptionTransitionCurlUp
completion:nil];
}
If you really, really want to use transitionFromView:
the only thing I could find that works is to just create a screen shot of the source view controller, and animate that.
- (void)perform
{
UIViewController *source = self.sourceViewController;
UIViewController *destination = self.destinationViewController;
// Create screenshot for animation
UIGraphicsBeginImageContextWithOptions(source.view.bounds.size, NO, 0.0);
CGContextRef context = UIGraphicsGetCurrentContext();
[source.view.layer renderInContext:context];
UIImageView *screenShot = [[UIImageView alloc] initWithImage:UIGraphicsGetImageFromCurrentImageContext()];
UIGraphicsEndImageContext();
// Put destination view controller and screen shot in place
UIWindow *window = source.view.window;
window.rootViewController = destination;
[window addSubview:screenShot];
[UIView transitionFromView:screenShot
toView:destination.view
duration:1.0
options:UIViewAnimationOptionTransitionCurlUp
completion:^(BOOL finished) {
}];
}
viewWillAppear:
and its ilk only get called once as expected.