Dan Morrow
Dan Morrow

Reputation: 4481

replace segue, with animation?

A colleague of mine just showed me the "replace segue" that I can use for my split-view controller. It does work well.

But I'd really like to do a "cross-fade" from the old view to the new view. I've done fades by setting the alpha property of a view, but not sure how this would work using segues.

Upvotes: 0

Views: 1349

Answers (3)

malhal
malhal

Reputation: 30754

The problem is transition animations require a container view, but when replacing a split's detail there is no suitable container view, the entire right side is replaced (usually with a new nav controller containing the detail). The split view has one view which it adds the two sides as subviews, if it had a container view for each side then this would have been easy. Alternatively you could use its view or even the window but then you need to translate frames. A work around involves snapshotting the previous detail controller before it disappears then adding it as a subview on the new detail controller and animating its removal. This is what Notes does to achieve the trash note animation with the new note appearing behind (although it doesn't use segues). If you want the same animation to work in portrait (like trashing a note) then because nested nav controllers are used its best to use the outermost navigation controller's view as the container for the animation, so that even the nav bar is included in the fade.

The great news is this can all be done inside a UIStoryboardSegue subclass. In prepareForSegue do not set self.detailViewController just yet. In the perform method before calling super use the sourceViewController.detailViewController to snapshot the view. Then call super perform. Not you can update that property with the destination (so you have it for next time). Then add this snapshot as a subview of the destination (or its navigation controller). CATransaction flush then do your transitionWithView to animate away the screenshot with a [snapshot removeFromSuperView]. Here is some sample code:

- (void)perform{
    RootViewController *rootController = self.sourceViewController;
    MasterViewController *previousMasterViewController = (MasterViewController *)rootController.collapseControllerForMaster.detailViewController;
    UIView *snapshotView = [previousMasterViewController.navigationController._outermostNavigationController.view snapshotViewAfterScreenUpdates:NO];

    MasterViewController *masterController = (MasterViewController *)[self.destinationViewController topViewController];
    rootController.collapseControllerForMaster.detailViewController = masterController;

    UIView *view = [previousMasterViewController.deleteBarButtonItem valueForKey:@"view"];
    CGRect rect = [view convertRect:view.frame toView:masterController.view.window];

    [super perform];
    [masterController.navigationController._outermostNavigationController.view addSubview:snapshotView];

    [CATransaction flush];

    [UIView beginAnimations:@"delete" context:NULL];
    [UIView setAnimationTransition:103 forView:masterController.navigationController._outermostNavigationController.view cache:YES];
    [UIView setAnimationDuration:0.5f];
    [UIView setAnimationPosition:CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect))];
    [snapshotView removeFromSuperview];

    [UIView commitAnimations];
}

Apologies for the use of private methods, I hate having to reimplement crucial missing public methods. FYI the collapse controller is my Split View Delegate controller object. You can perhaps just use the detailViewController property on the source controller.

This is what it looks like in portrait:

enter image description here

Upvotes: 2

user2535467
user2535467

Reputation:

Change the size of the destination view to "Detail" size in the attributes inspector. Using a modal segue with the cross dissolve animation, set the segue presentation size to "Current Context".

That should get the detailView to cross dissolve and leave the masterView unaffected.

EDIT

Well, I have tried several options, but my time has come to an end for now. I think that by the time I get back you may have already found an answer, but this is the closest that I have gotten.

Create a method to fade out the DetailViewController like so:

- (void)fadeOut
{
    if (self.view.alpha > 0.0)
    {
        self.view.alpha = self.view.alpha - 0.01;
        [self performSelector:@selector(fadeOut) withObject:nil afterDelay:0.005];
    }
}

And then call the fadeOut method just before using the Replace segue. In the new ViewController, you can fade it in when it's called by the Replace segue using:

- (void)fadeIn
{
    if (self.view.alpha > 0.0)
    {
        self.view.alpha = self.view.alpha + 0.01;
        [self performSelector:@selector(fadeIn) withObject:nil afterDelay:0.005];
    }
}

You will have to play around with the timing, and it might look weird (steppy and whatnot), I haven't tested out the full concept yet.

The above is a bit of hackery, and probably not the best way of going about it, but that is what I have so far. If you haven't already figured it out, I'll see what else I can do and edit accordingly.

Upvotes: 0

hgwhittle
hgwhittle

Reputation: 9426

Try this:

  1. Select your segue in the Storyboard
  2. In the Attributes inspector, set the Style of the segue to 'Modal'.
  3. Set the Transition to 'Cross Dissolve'
  4. Try it out and see if that's what you're looking for.

Upvotes: 0

Related Questions