0

我目前正在测试新的 iOS 7 视图控制器转换。我想要的是一个自定义模态呈现过渡,它呈现你的下一个视图从屏幕顶部切割成几个条带。每个条带应在增加延迟后出现,以提供所需的效果。

所以我的代码如下所示:

- (void)presentModalWithContext:(id<UIViewControllerContextTransitioning>)context
{
    UIView *inView = [context containerView];

    UIView *fromView = [context viewControllerForKey:UITransitionContextFromViewControllerKey].view;
    UIView *toView = [context viewControllerForKey:UITransitionContextToViewControllerKey].view;

    NSTimeInterval stripTime = 1.0;
    NSTimeInterval stripDelay = 1.0;
    NSInteger stripCount = 10;
    CGFloat stripHeight = toView.frame.size.height / stripCount;

    for (NSInteger i = 0; i < stripCount; i++)
    {
        CGFloat offsetY = i*stripHeight;
        CGRect snapRect = CGRectMake(0, offsetY, toView.frame.size.width, stripHeight);

        UIView *view = [toView resizableSnapshotViewFromRect:snapRect afterScreenUpdates:YES withCapInsets:UIEdgeInsetsZero];
        CGRect stripRect = CGRectMake(0, -(stripCount-i)*stripHeight, snapRect.size.width, snapRect.size.height);
        view.frame = stripRect;

        [inView insertSubview:view aboveSubview:fromView];

        NSTimeInterval interval = stripDelay*(stripCount-i);

        [UIView animateWithDuration:stripTime delay:interval options:0 animations:^{
            CGPoint center = view.center;
            center.y += stripCount*stripHeight;
            view.center = center;
        } completion:^(BOOL finished) {
            NSLog(@"complete");
            if (i == stripCount-1)
                [context completeTransition:YES];
        }];
    }
}

我已经检查了每个条带的初始和最终位置,并且已经可以了。我的interval变量也在每个循环中正确设置。

但这似乎并没有延迟。所有条带都在一起移动,给人的印象是整个视图都在移动。

快速查看基本日志显示所有动画都是同时执行的:

2013-09-20 01:11:32.908 test_transition[7451:a0b] complete
2013-09-20 01:11:32.909 test_transition[7451:a0b] complete
2013-09-20 01:11:32.910 test_transition[7451:a0b] complete
2013-09-20 01:11:32.910 test_transition[7451:a0b] complete
2013-09-20 01:11:32.911 test_transition[7451:a0b] complete
2013-09-20 01:11:32.911 test_transition[7451:a0b] complete
2013-09-20 01:11:32.912 test_transition[7451:a0b] complete
2013-09-20 01:11:32.912 test_transition[7451:a0b] complete
2013-09-20 01:11:32.913 test_transition[7451:a0b] complete
2013-09-20 01:11:32.913 test_transition[7451:a0b] complete

有人能够发现这里有什么问题吗?

编辑 :

似乎这是取消任何动画延迟的以下行,即使这些与被快照的视图无关:

UIView *view = [toView resizableSnapshotViewFromRect:snapRect afterScreenUpdates:YES withCapInsets:UIEdgeInsetsZero];

如果我将参数设置afterScreenUpdatesNO,则视图快照是null并且我得到以下错误日志:

Snapshotting a view that has not been rendered results in an empty snapshot. Ensure your view has been rendered at least once before snapshotting or snapshot after screen updates.

如何在快照之前渲染视图?我试过[toView setNeedsDisplay]但没有成功......

4

3 回答 3

2

这是一个解决方案。

虽然这个问题已经有 2 年了,但它仍然是一个相关的问题,因为它仍然存在于 iOS9 上。我意识到这对提问者来说没有多大帮助,因为它已经 2 年了,但我只是遇到了这个。所以这里有一个解决方案。

当你想在视图控制器之间转换时,你可能会使用一个 Animator 对象来运行你自定义的 UIView 块动画代码。这可能是复杂的多个动画块和一些使用延迟值。但是,如果在过渡期间您想要捕获或截取某些内容的一部分(无论是通过 UIView.drawViewHierarchyInRect、UIView.snapshotViewAfterScreenUpdates 还是 UIView.resizableSnapshotViewFromRect),使用这些方法中的任何一种都将解除动画中的延迟。对于最后两种方法,如果你为 afterScreenUpdates 传递 false,那么它不会解除延迟,但它也不会捕获任何东西;捕捉某些东西必须为真,但将其设置为真会解除您的延迟。

使用这些方法中的任何一种都会解除动画块中的延迟,并且通常会搞砸。如果你告诉 UIKit 过渡将是 3 秒,并且你有一个动画块 (UIView.animateWithDuration...),它有 2 秒延迟和 1 秒动画,如果你的延迟被解除,那么动画会立即运行并且过渡持续只需 1 秒,这会使 UIKit 不同步,并且通常会变得混乱,因为它期望它是 3 秒。

这是一个解决方案:假设您正在从视图控制器 A 转换到视图控制器 B。在 Animator 对象的代码文件(继承自 NSObject 并符合 UIViewControllerAnimatedTransitioning 协议的对象)中,您将动画代码放在 animateTransition(transitionContext: UIViewControllerContextTransitioning ) { ...} 委托方法。如果你从 VC A 过渡到 VC B,假设你想从 VC B 截取一些东西,并在过渡期间展示它并用它做一些事情。一种完美的方法是使用 View Controller B 的 viewDidLoad 方法中的 drawViewHierarchyInRect 方法,在 View Controller B 的实例属性中捕获和存储图像(或从该图像创建 UIImageView 并存储 UIImageView)。您需要使用 drawViewHierarchyInRect 方法而不是其他两个方法,因为它们要求内容在屏幕上可见(即已经渲染);drawViewHiearchyInRect 方法捕获 offScreen 内容,即使它没有添加到视图中。

一旦您开始转换,“to view controller”就会被初始化,并且 viewDidLoad 会被调用。因此,在您的过渡动画代码中,您可以通过引用视图控制器的属性来抓取您截屏的图像(或 imageView),并在过渡中使用它做任何您想做的事情。您的延误不会被解除。

要点:不要在过渡期间截屏。相反,将屏幕截图代码放在视图控制器的 viewDidLoad 中,并将其输出存储在实例变量中,并在您的转换中引用它。

希望这会有所帮助,我今天才遇到这个问题,刚刚遇到了一个解决方案。

于 2016-05-03T15:09:54.363 回答
1

在对您的代码进行了一段时间处理并将其与我的代码进行比较之后,延迟参数得到了正确的尊重,我仍然无法弄清楚为什么您的代码不起作用。无论如何,我找到了另一种可行的方法。我将动画分成两部分。在第一部分中,我使用您的代码创建视图切片,将它们添加到 inView 以及可变数组中。在第二部分中,我没有延迟地递归调用动画块,直到显示最后一个片段。这种方法的一个限制是,每个片段动画必须在下一个动画开始之前完成(因为下一个动画是从完成块调用的),因此您无法独立控制持续时间和延迟。无论如何,这就是我所做的。在呈现视图控制器中,我只是这样做:

-(IBAction)presntBlue:(id)sender {
    BlueViewController *blue = [self.storyboard instantiateViewControllerWithIdentifier:@"Blue"];
    blue.modalTransitionStyle = UIModalPresentationCustom;
    blue.transitioningDelegate = self;
    [self presentViewController:blue animated:YES completion:nil];
}

-(id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
    RDPresentationAnimator *animator = [RDPresentationAnimator new];
    animator.isPresenting = YES;
    return animator;
}

在 RDPresentationAnimator 类中,我有这个:

@interface RDPresentationAnimator () {
    NSInteger stripCount;
    CGFloat stripHeight;
    NSMutableArray *stripArray;
}
@end

@implementation RDPresentationAnimator

#define ANIMATION_TIME .3

- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
    return ANIMATION_TIME;
}


- (void)animateTransition:(id<UIViewControllerContextTransitioning>)context {
    UIView *inView = [context containerView];
    UIView *toView = [context viewControllerForKey:UITransitionContextToViewControllerKey].view;

    stripCount = 10;
    stripHeight = toView.frame.size.height / stripCount;
    stripArray = [NSMutableArray new];
    for (NSInteger i = 0; i < stripCount; i++)
    {
        CGFloat offsetY = i*stripHeight;
        CGRect snapRect = CGRectMake(0, offsetY, toView.frame.size.width, stripHeight);

        UIView *view = [toView resizableSnapshotViewFromRect:snapRect afterScreenUpdates:YES withCapInsets:UIEdgeInsetsZero];
        CGRect stripRect = CGRectMake(0, -(stripCount-i)*stripHeight, snapRect.size.width, snapRect.size.height);
        view.frame = stripRect;
        [inView addSubview:view];
        [stripArray addObject:view];
    }
    [self animateStrip:stripCount - 1 withContext:context];
}

-(void)animateStrip:(NSInteger) index withContext:(id <UIViewControllerContextTransitioning>) context{
    [UIView animateWithDuration:ANIMATION_TIME animations:^{
        UIView *view = stripArray[index];
        CGPoint center = view.center;
        center.y += stripCount*stripHeight;
        view.center = center;
    } completion:^(BOOL finished) {
        if (index >0) {
            [self animateStrip:index - 1 withContext:context];
        }else{
            [context completeTransition:YES];
        };
    }];
}
于 2013-09-21T04:33:18.690 回答
1

我想我会添加另一个答案,它可以让您独立控制 stripTime 和 stripDelay。我从来没有找到使用新的 UIViewControllerContextTransitioning 方法使其工作的方法。这种方式使用普通的 UIView 动画,然后是无动画 presentViewController。此方法应该在纵向或横向方向上都能正常工作(请注意,我使用 self.view.bounds 来计算 stripHeight 和 snapRect,因此这些值对于任一方向都是正确的)。

@interface ViewController () {
    NSInteger stripCount;
    CGFloat stripHeight;
    NSMutableArray *stripArray;
}
@end

@implementation ViewController

-(IBAction)presntBlue:(id)sender {
    BlueViewController *blue = [self.storyboard instantiateViewControllerWithIdentifier:@"Blue"];
    [self animateView:blue];
}

-(void)animateView:(UIViewController *) toVC; {
    UIView *toView = toVC.view;
    toView.frame = self.view.bounds;
    [self.view addSubview:toView];
    NSTimeInterval stripDelay = 0.2;
    NSTimeInterval stripTime = 1.0;
    stripCount = 10;
    stripHeight = self.view.bounds.size.height / stripCount;
    stripArray = [NSMutableArray new];
    for (NSInteger i = 0; i < stripCount; i++) {

        CGFloat offsetY = i*stripHeight;
        CGRect snapRect = CGRectMake(0, offsetY, self.view.bounds.size.width, stripHeight);
        UIView *view = [toView resizableSnapshotViewFromRect:snapRect afterScreenUpdates:YES withCapInsets:UIEdgeInsetsZero];
        CGRect stripRect = CGRectMake(0, -(stripCount-i)*stripHeight, snapRect.size.width, snapRect.size.height);
        view.frame = stripRect;
        [self.view addSubview:view];
        [stripArray addObject:view];
    }
    [toView removeFromSuperview];

    for (NSInteger i = 0; i < stripCount; i++) {
        NSTimeInterval interval = stripDelay*(stripCount-i);
        UIView *view = stripArray[i];
        [UIView animateWithDuration:stripTime delay:interval options:0 animations:^{
            CGPoint center = view.center;
            center.y += stripCount*stripHeight;
            view.center = center;
        } completion:^(BOOL finished) {
            if (i == 0){
                [self presentViewController:toVC animated:NO completion:nil];
            }
        }];
    }
}

补充说明:

在 animateView: 方法中,我将 toView 添加到 self.view,然后在制作条带后将其删除。我这样做是为了确保它在纵向和横向上都能正常工作——如果我省略这两个语句,当动画完成时横向动画会出现轻微的故障。如果我有这两行,我偶尔会在开始时遇到一个小故障,你可以看到整个 toView 的短暂闪光。我不知道为什么这种情况只是偶尔发生,而且我还没有更新我的手机,所以我不知道设备上是否也会发生这种情况。

于 2013-09-21T17:24:15.717 回答