How can I use CATransition to animate a portion of the screen when pushing from one UIViewController to another

StackOverflow https://stackoverflow.com/questions/12390395

Question

I have a couple of UIViewControllers that consist of the same information on the right side but different information on the left side of the screen. I want to transition from one to the other but only animate the left side, i.e. slide the left side up while leaving the right side static.

-(void)goNextStep{
    UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle: nil];
    NSString *identifier = @"";
    if (_model.isLoggedIn) {
        //The user is already logged in, don't display the loginViewController
        // instead take them to the next viewController
        identifier = [_model nextStepIdentifier];
        if ([identifier isEqualToString:@""]) {
            identifier = @"loginController";
        }
    }else{
        identifier = @"loginController";
    }

    UIViewController *nextVC = (UIViewController *)[mainStoryboard instantiateViewControllerWithIdentifier:identifier];

    [nextVC setHidesBottomBarWhenPushed:YES];
    CATransition *transition = [CATransition animation];
    transition.duration = 0.5f;
    transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    transition.type = kCATransitionPush;
    transition.subtype = kCATransitionFromBottom;
    transition.delegate = self;
    //I would love to have
    //transition.rect=MyRect;
    [self.navigationController.view.layer addAnimation:transition forKey:nil];

    [self.navigationController pushViewController:nextVC animated:NO];
}

I would like to restrict this transition to a defined rect of the screen.

Is there a way to restrict the transition animation to a rect? If so, how?

Was it helpful?

Solution 2

Ok, I could not find a way to handle this transition in the initial push from viewController A to viewController B, however I found a workaround that seems to work nicely, although I have some more massaging to get it to work exactly the way I need, this is in the right direction.

First I placed a UIImageView in viewController B and made an outlet in that viewController. I also gave this UIImageView a tag, 1000 worked for me.

Next I altered my code in viewController A to the following:

-(void)goNextStep{
    UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle: nil];
    NSString *identifier = @"";
    if (_model.isLoggedIn) {
        //The user is already logged in, don't display the loginViewController
        // instead take them to the next viewController
        identifier = [_model nextStepIdentifier];
        if ([identifier isEqualToString:@""]) {
            identifier = @"loginController";
        }
    }else{
        identifier = @"loginController";
    }

    UIViewController *nextVC = (UIViewController *)[mainStoryboard instantiateViewControllerWithIdentifier:identifier];

    UIImageView *infoImageView = [nextVC infoImageView];
    infoImageView.image = [self transitionImageFromView:self.transImageRect];

    [nextVC setHidesBottomBarWhenPushed:YES];
    [self.navigationController pushViewController:nextVC animated:NO];
}

So before pushing to the next viewController (B) I grab a screenshot of the left side of viewController A and set the image I just added to viewController B to that screenshot.

Now in ViewController B I added the following, please ignore the untidy code.

-(void)viewDidAppear:(BOOL)animated{
    CGRect stage = self.view.bounds;
    CGRect actor =self.formView.frame;
    CGRect spot = actor;
    CGRect cover= self.transitionView.frame;
    cover.origin.y=(0.0 - cover.size.height);
    actor.origin.y = stage.size.height;
    self.formView.frame=actor;

    [UIView animateWithDuration:0.5 animations:^{
        self.formView.frame=spot;
        self.transitionView.frame=cover;

    }];

}

#define kINFO_VIEW_TAG  1000

// answer the info image view
// add this declaration to view controller B .h file
//
- (UIImageView *)infoImageView {
    return (UIImageView *)[self.view viewWithTag:kINFO_VIEW_TAG];
}

This handles the transition moving the UIImageView up and the UIView with the login form into place. It looks like I don't even need CATransition.

OTHER TIPS

There isn't a way to do this with the navigation controller push. VCs, especially the container VCs are designed around the idea that only one at a time controls the interface. Even that navigationController.view.layer is not yours to manipulate.

The school solution for something like this is to implement the non-changing, right side of the interface with a view that has a delegate (and maybe a datasource) outlet. Think UITableView. The view controllers that want to have this view add it to their hierarchy in the traditional way. It might have the same datasource (and even the same delegate) no matter which VC it appears on.

But how to achieve the desired transition? I can't think of a way to get exactly what you're looking for (that is, no change at all on the right with a normal push transition on the left), but consider this:

1) if the info-view is all set up in the same state on the pushed vc before the push, the transition is going to look pretty good. 2) if the info-view has a slide-closed animation, where it get's really skinny - just wide enough to provide an "open" control - then you can close it before the push transition, set it up closed on the next VC, and optionally open it again after the push.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top