替换 UIView 时假的推送和弹出动画

Fake push and pop animation when replace UIView

出于某些原因,我必须使用 addSubviewaddChildViewController 来替换视图而不是 push/pop 视图控制器。问题是我想在更改 UIViewController (push/pop) 时完全伪造动画。 这是我的尝试:

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

此代码不作为方面工作,因为下一个控制器从底部移动到顶部(推),并且当它移动时,它后面有一个空白背景(根控制器的背景)。

所以我需要正确的方法来伪造推送和弹出动画(上拉和下拉)。
如有任何帮助,我们将不胜感激。

这篇文章似乎与您正在尝试做的事情相关?

When to use addChildViewController vs pushViewController

似乎建议您可以使用 transitionFromViewController:toViewController:duration:options:animations:completion: 来设置您自己管理的控制器之间的动画转换。

我在一个应用程序中使用了这个效果。我想要(并且拥有)嵌入到 iPad 应用程序边缘的 iPhone 系列选择屏幕的外观。因此,用户按下列表中的一个按钮,然后屏幕转换(看起来像 UIViewController 前进)到更多选项,尽管它都在边缘的 window 中(相关内容会在右侧弹出)。这是我使用的代码:

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

...这是截图者:

+ (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;
}

.. 最后是 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

我留下了一些我独有的东西,因为你可能需要类似的电话。例如 [self updateData] 会考虑系统状态来更新模型并设置任何其他基于状态的调用。当屏幕应该被跳过时也有一个例外我已经留下了。你可以安全地删除那些,尽管你可能需要类似的变体(因为你并没有真正改变 UIViewController 的)。

我唯一要注意的是,除非你有像我这样的极端案例——在 iPad 或类似的东西的弹出视图中模仿 iPhone——最好只使用真正的 UIVewController。