ios 7 - 具有延迟的UIView动画方法不会延迟

时间:2013-09-19 23:04:53

标签: ios animation delay transition ios7

我目前正在测试新的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];

如果我将参数afterScreenUpdates设置为NO,则视图快照为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]但没有成功......

3 个答案:

答案 0 :(得分: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];
        };
    }];
}

答案 1 :(得分:1)

我以为我会添加另一个答案,它可以让你独立控制stripTime和stripDelay。我从未找到使用新的UIViewControllerContextTransitioning方法使其工作的方法。这种方式使用普通的UIView动画,然后是no animation 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的短暂闪光。我不知道为什么这只会偶尔发生,我还没有更新我的手机,所以我不知道这是否也会在设备上发生。

答案 2 :(得分:0)

这是一个解决方案。

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

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

使用这些方法中的任何一种都可以解除动画块中的延迟,并且通常会搞砸。如果你告诉UIKit转换将是3秒并且你有一个2秒延迟和1秒动画的动画块(UIView.animateWithDuration ...),如果你的延迟被解除,那么动画会立即运行并且过渡持续只用了1秒,这让UIKit失去了同步,而且一般都搞砸了,因为它预计会持续3秒。

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

一旦开始过渡,就可以查看控制器&#39;初始化并调用它的viewDidLoad。因此,在过渡动画代码中,您可以通过参考视图控制器的属性来获取截屏图像(或图像视图),并在转换过程中随意执行任何操作。您的延误不会被解除。

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

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