viewWillAppear called twice with custom segue anim

2019-03-27 06:55发布

问题:

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?

回答1:

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


回答2:

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


回答3:

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.