3

For some reasons, I have to use addSubview and addChildViewController to replace view instead of push/pop view controller. The problem is that I want to fake exactly the animation when changing UIViewController (push/pop). Here is my try:

In RootViewController.m

// switch controller view
currentController = nextViewController;

[self addChildViewController:currentController]; 
[self.view addSubview:currentController.view];

// pop - move down
[currentController.view setFrame:CGRectMake(self.view.frame.size.width, 0, currentController.view.frame.size.width, currentController.view.frame.size.height)];

        [UIView animateWithDuration:0.5
                         animations:^{
                             [currentController.view setFrame:CGRectMake(self.view.frame.size.width, self.view.frame.size.height, currentController.view.frame.size.width, currentController.view.frame.size.height)];
                             [self addConstrainForView];
                         }];


//Push - move up
  [currentController.view setFrame:CGRectMake(0, self.view.frame.size.height, currentController.view.frame.size.width, currentController.view.frame.size.height)];
        [UIView animateWithDuration:0.5
                         animations:^{
                             [currentController.view setFrame:CGRectMake(0, 0, currentController.view.frame.size.width, currentController.view.frame.size.height)];
                             [self addConstrainForView];
                         }];

This code doesn't work as aspect because the next controller move from bottom to top (push), and when it is moving, there is a blank background behind it (root controller 's background).

So I need the correct way to fake push and pop animation (pull up & pull down).
Any help would be appreciated.

ductran
  • 10,043
  • 19
  • 82
  • 165
  • Did you noticed that you first set the frame and then animate to the same frame at the pop - move down? – LoVo Mar 30 '15 at 14:46
  • I've done this before. Don't have time to write a full answer but you have to take an image of the prior screen then put it up over the next one before you start the animation so it looks like a real UIViewController. I use it for embedded lists on an iPad, to give the iPhone feel that you're scrolling through lists on an iPad. Only caveat is that, at least in my implementation, you have to take account of the state of the screen if it scrolls or it looks like it jerks around when you start the animation. – Michael Olenick Mar 30 '15 at 14:55
  • Should your pop not be going from 0,0 to 0,height? You seem to go from width,0 to width,height. Is that not all offscreen? – Rory McKinnel Mar 30 '15 at 15:00
  • @MichaelOlenick could you share some peaces of code? I really don't know how to do like it. – ductran Mar 30 '15 at 15:02
  • @RoryMcKinnel I just need to move the y axis in this case. Also, the problem I mention is a black screen on behind when doing animation – ductran Mar 30 '15 at 15:03
  • @MichaelOlenick. That was my point. It is using the x coordinate set as the width from start to finish, so that is not on screen. The height goes from 0 to height, but the width looks wrong in your code snippet. – Rory McKinnel Mar 30 '15 at 15:07
  • Faking push/pop is one of the animations when it's easier to use the predefined transitions provided by the low level animation API - `CATransaction` and a `CATransition`. – Sulthan Mar 30 '15 at 15:12

2 Answers2

1

I use this effect in an app. I wanted (and have) the look of an iPhone series of pick screens embedded into the edge of an iPad app. So the user presses a button from a list then the screen transitions, that looks like a UIViewController advancing, to more options, though it's all in a window on the edge (and the relevant content pops up on the right). Here's the code I use:

- (void)advanceLevel
{
    UIImageView *screenShot = [self getTableScreenShot];
    [self.tableView.superview addSubview:screenShot];

    // Put screen shot over the whole thing with mask for iPhone style animation
    TableViewMask *mask =
            [[TableViewMask alloc] initWithFrame:CGRectMake(0, 0, 1024, 768) withImage:[Util TakeScreenshot]];
    [self.tableView.superview addSubview:mask];

    tableLevel++;
    [self updateData];

    [UIView animateWithDuration:.6 animations:^
    {
        screenShot.frame =
        CGRectMake(self.tableView.frame.origin.x-self.tableView.frame.size.width, self.tableView.frame.origin.y,
                   self.tableView.frame.size.width, self.tableView.frame.size.height);
        controller.toolbar.backButton.alpha = (tableLevel>1?1:0);
    }
    completion:^(BOOL finished)
    {
        [screenShot removeFromSuperview];
        [mask removeFromSuperview];
        [self updateUI];
    }];
}

- (void)goBackLevel
{
    if(tableLevel==1){ return; }

    if(tableLevel==3 && isEditing==YES)
    {
        // Skip this screen if coming from higher screens
        tableLevel--;
    }

    UIImageView *screenShot = [self getTableScreenShot];
    [self.tableView.superview addSubview:screenShot];

    // Put screen shot over the whole thing with mask for iPhone style animation
    TableViewMask *mask = [[TableViewMask alloc] initWithFrame:CGRectMake(0, 0, 1024, 768) withImage:[Util TakeScreenshot]];
    [self.tableView.superview addSubview:mask];

    tableLevel--;
    [self updateData];

    [UIView animateWithDuration:.6 animations:^
    {
        screenShot.frame =
            CGRectMake(self.tableView.frame.origin.x+self.tableView.frame.size.width, self.tableView.frame.origin.y,
                       self.tableView.frame.size.width, self.tableView.frame.size.height);
        controller.toolbar.backButton.alpha = (tableLevel>1?1:0);
    }
    completion:^(BOOL finished)
    {
        [screenShot removeFromSuperview];
        [mask removeFromSuperview];
        [self updateUI];
    }];
}

... here's the screen-shot taker:

+ (UIImage *)TakeScreenshot
{
    CGSize imageSize = [[UIScreen mainScreen] bounds].size;

    UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0);

    CGContextRef context = UIGraphicsGetCurrentContext();

    // Iterate over every window from back to front
    for (UIWindow *window in [[UIApplication sharedApplication] windows])
    {
        if (![window respondsToSelector:@selector(screen)] || [window screen] == [UIScreen mainScreen])
        {
            // -renderInContext: renders in the coordinate space of the layer,
            // so we must first apply the layer's geometry to the graphics context
            CGContextSaveGState(context);
            // Center the context around the window's anchor point
            CGContextTranslateCTM(context, [window center].x, [window center].y);
            // Apply the window's transform about the anchor point
            CGContextConcatCTM(context, [window transform]);
            // Offset by the portion of the bounds left of and above the anchor point
            CGContextTranslateCTM(context,
                                  -[window bounds].size.width * [[window layer] anchorPoint].x,
                                  -[window bounds].size.height * [[window layer] anchorPoint].y);

            // Render the layer hierarchy to the current context
            [[window layer] renderInContext:context];

            // Restore the context
            CGContextRestoreGState(context);
        }
    }

    // Retrieve the screenshot image
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return image;
}

.. and finally TableViewMask:

@implementation TableViewMask

UIImage *screenShotImage;
- (instancetype)initWithFrame:(CGRect)frame withImage:(UIImage *)_screenShotImage
{
    self = [super initWithFrame:frame];
    if(self)
    {
        self.backgroundColor = [UIColor clearColor];

        screenShotImage = _screenShotImage;

        [self setNeedsDisplay];
    }
    return self;
}

- (void)drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();

    [screenShotImage drawInRect:rect];

    CGRect intersection = CGRectIntersection( CGRectMake(10.2, 130, 299.6, 600), rect );
    if( CGRectIntersectsRect( intersection, rect ) )
    {
        CGContextFillRect(context, CGRectMake(10, 130, 300, 600));
        CGContextClearRect(context, intersection);
        CGContextSetFillColorWithColor( context, [UIColor clearColor].CGColor );
        CGContextFillRect( context, intersection);
    }
}

@end

I left in some things that are unique to mine because you'll likely need similar calls. For example [self updateData] takes the state of the system into account to update the model and set any other state-based calls. There's also an exception when a screen should be skipped I've left in. You can safely remove those, though you'll probably need similar variants (since you're not really changing UIViewController's).

The only caveat I'd have with this is that unless you have a corner case like mine -- imitating an iPhone in a popped out view on an iPad or something similar -- it's probably better just to use real UIVewController's.

Michael Olenick
  • 458
  • 4
  • 11
0

This article would seem relevant to what you are trying to do?

When to use addChildViewController vs pushViewController

It seems to suggest you can use transitionFromViewController:toViewController:duration:options:animations:completion: to animate transition between controllers you are managing yourself.

Community
  • 1
  • 1
Rory McKinnel
  • 7,936
  • 2
  • 17
  • 28
  • Try many ways, it always crash: Children view controllers and must have a common parent view controller when calling -[UIViewController transitionFromViewController:toViewController:duration:options:animations:completion – ductran Mar 31 '15 at 11:43
  • This mechanism seems to be how you are supposed to do it. There are some steps you need like calling `didMoveToParentViewController` for the new one and `willMoveFromParentViewController` on the old one. Found this question answer which seems more complete: http://stackoverflow.com/questions/8453653/proper-usage-of-transitionfromviewcontrollertoviewcontrollerdurationoptionsa – Rory McKinnel Mar 31 '15 at 13:47