简体   繁体   English

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

[英]ios 7 - UIView animate method with delay is not delayed

I'm currently testing new iOS 7 views controller transition. 我目前正在测试新的iOS 7视图控制器转换。 What i want is a custom modal presenting transition that present your next view cut into several strip from top off screen. 我想要的是一个自定义模式呈现过渡,将您的下一个视图从顶部屏幕切割成几个条带。 Each strip should appear after an incremental delay to give the desired effect. 每个条带应在增量延迟后出现以产生所需效果。

So my code looks like this : 所以我的代码看起来像这样:

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

I've already checked initial and final position of each strip and already is OK. 我已经检查了每个条带的初始和最终位置,并且已经可以了。 My interval variable is also properly set at each loop. 我的interval变量也在每个循环中正确设置。

But it seems that this is not delayed at all. 但似乎这根本没有延迟。 All strips are moving together, giving the impression that the complete view is moving. 所有条带都在一起移动,给人一种完整视图移动的印象。

A quick look to basic log shows that all animations are performed at the same time : 快速查看基本日志可以看到所有动画同时执行:

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

Do someone is able to spot what's wrong here ? 有人能够发现这里有什么问题吗?

EDIT : 编辑:

It seems this is the following line that cancel the delay of any animations, even if those are not concerning the view being snapshotted : 似乎这是取消任何动画延迟的以下行,即使这些动画与正在拍摄的视图无关:

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

If i set the parameter afterScreenUpdates to NO , the view snapshot is null and i get the following error log : 如果我将afterScreenUpdates的参数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.

How do i render the view before snapshotting ? 如何在快照之前渲染视图? I tried [toView setNeedsDisplay] but with no success ... 我试过[toView setNeedsDisplay]但没有成功......

After working on your code for a bit, and comparing it to mine, where the delay parameter was honored correctly, I still can't figure out why yours doesn't work. 在对你的代码进行了一些处理,并将它与我的比较,其中延迟参数被正确认可后,我仍然无法弄清楚为什么你的代码不起作用。 In any case, I found another way that does work. 无论如何,我找到了另一种方法。 I break the animation into two parts. 我将动画分为两部分。 In the first part, I create the slices of the view using your code, add them to the inView, and also to a mutable array. 在第一部分中,我使用您的代码创建视图切片,将它们添加到inView,还添加到可变数组。 In the second part, I call the animation block recursively, with no delay, until the last strip is displayed. 在第二部分中,我递归地调用动画块,没有延迟,直到显示最后一个条带。 One limitation to this approach, is that each strip animation has to complete before the next one begins (since the next one is called from the completion block), so you don't have independent control over the duration and delay. 这种方法的一个限制是每个条带动画必须在下一个开始之前完成(因为下一个从完成块调用),因此您无法独立控制持续时间和延迟。 Anyway, here is what I did. 无论如何,这就是我所做的。 In the presenting view controller, I just do this: 在呈现视图控制器中,我只是这样做:

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

And in the RDPresentationAnimator class, I have this: 在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];
        };
    }];
}

I thought I'd add another answer that does give you the independent control over the stripTime and stripDelay. 我想我会添加另一个答案,它可以让你独立控制stripTime和stripDelay。 I never did find a way to make it work using the new UIViewControllerContextTransitioning methods. 我从未找到使用新的UIViewControllerContextTransitioning方法使其工作的方法。 This way uses normal UIView animations, followed by a no animation presentViewController. 这种方式使用普通的UIView动画,然后是no animation presentViewController。 This method should work correctly in either portrait or landscape orientation (notice that I use self.view.bounds to calculate stripHeight and snapRect, so that those values will be correct for either orientation). 此方法应该在纵向或横向方向上正常工作(请注意,我使用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];
            }
        }];
    }
}

Added note: 补充说明:

In the animateView: method, I add the toView to self.view,, and then remove it after making the strips. 在animateView:方法中,我将toView添加到self.view,然后在制作条带后将其删除。 I do this to make sure it works correctly in portrait and landscape -- if I omit those two statements, there's a slight glitch in the landscape animation when the animation finishes. 我这样做是为了确保它在纵向和横向上正常工作 - 如果我省略这两个陈述,动画结束时景观动画中会出现轻微的故障。 If I have those two lines in, I occasionally get a glitch at the beginning where you can see the whole toView for a brief flash. 如果我有这两行,我偶尔会遇到一个小故障,你可以看到整个toView的短暂闪光。 I don't know why this only happens occasionally, and I haven't updated my phone yet, so I don't know if this happens on the device as well. 我不知道为什么这只会偶尔发生,我还没有更新我的手机,所以我不知道这是否也会在设备上发生。

Here's a solution. 这是一个解决方案。

Although this question is 2 years old, it's still a pertinent one as it still exists on iOS9. 虽然这个问题是2年之久,但它仍然是一个相关的问题,因为它仍然存在于iOS9上。 I realize it miiight not be as much help to the asker seeing it's been 2 years, but I only just came across this. 我意识到这一点并没有给提问者看到它已经2年的帮助,但我只是遇到了这个问题。 So here's a solution. 所以这是一个解决方案。

When you want to transition between view controllers, you're probably gonna be using an Animator Object to run your custom UIView block animation code. 当您想要在视图控制器之间进行转换时,您可能会使用Animator对象来运行自定义UIView块动画代码。 This might be sophisticated with multiple animation blocks and some using a delay value. 这可能是复杂的多个动画块,有些使用延迟值。 But if during your transition you want to capture or screenshot a portion of something (whether it's by UIView.drawViewHierarchyInRect, UIView.snapshotViewAfterScreenUpdates, or UIView.resizableSnapshotViewFromRect), using any of these methods will disengage the delays in your animation. 但是如果在转换过程中你想捕获或截取某些内容(无论是UIView.drawViewHierarchyInRect,UIView.snapshotViewAfterScreenUpdates还是UIView.resizableSnapshotViewFromRect),使用这些方法中的任何一种都可以解除动画中的延迟。 For the last 2 methods, if you pass false for afterScreenUpdates, then it won't disengage the delays, but it also won't capture anything; 对于最后两种方法,如果你为afterScreenUpdates传递false,那么它不会解除延迟,但它也不会捕获任何东西; it has to be true to capture something, but setting it to true will disengage your delay. 它必须是真实的捕捉某些东西,但将其设置为真将解除你的延迟。

Using any of these methods will disengage the delay in your animation block, and generally mess things up. 使用这些方法中的任何一种都可以解除动画块中的延迟,并且通常会搞砸。 If you tell UIKit the transition is gonna be 3 secs and you have an animation block (UIView.animateWithDuration...) that has a 2 sec delay and 1 sec animation, if your delay gets disengaged then the animation runs instantly and the transition lasts just 1 sec, which throws UIKit out of sync and stuff gets generally messed up cuz it was expecting it to be 3 secs. 如果你告诉UIKit转换将是3秒并且你有一个2秒延迟和1秒动画的动画块(UIView.animateWithDuration ...),如果你的延迟被解除,那么动画会立即运行并且过渡持续仅仅1秒,这使得UIKit失去同步,并且一般都搞砸了,因为它预计会是3秒。

Here's a solution: Say you're transition from view controller A to view controller B. In your Animator Object's code file (an object that inherits from NSObject and conforms to UIViewControllerAnimatedTransitioning protocol), you put your animation code in the animateTransition(transitionContext: UIViewControllerContextTransitioning) { ...} delegate method. 这是一个解决方案:假设你是从视图控制器A转换到视图控制器B.在Animator对象的代码文件(一个继承自NSObject并符合UIViewControllerAnimatedTransitioning协议的对象)中,你将动画代码放在animateTransition中(transitionContext:UIViewControllerContextTransitioning) ){...}委托方法。 If you're transitioning from VC A to VC B, say you want to screenshot something from VC B, and show it and do something with it during the transition. 如果您正在从VC A转换到VC B,请说您希望从VC B中截取某些内容,并在转换过程中显示它并对其执行某些操作。 One way that works perfectly, is to use the drawViewHierarchyInRect method in View Controller B's viewDidLoad method, to capture and store the image (or create a UIImageView from that image and store the UIImageView) in an instance property of View Controller B. You need to use the drawViewHierarchyInRect method and not any of the other two because those require the content to be visible on screen (ie already rendered); 一种完美的方法是在View Controller B的viewDidLoad方法中使用drawViewHierarchyInRect方法,捕获并存储图像(或从该图像创建UIImageView并存储UIImageView)在View Controller B的实例属性中。您需要使用drawViewHierarchyInRect方法而不是其他两个方法,因为那些要求内容在屏幕上可见(即已经渲染); the drawViewHiearchyInRect method captures offScreen content, even if it's not added to the view. drawViewHiearchyInRect方法捕获offScreen内容,即使它没有添加到视图中。

As soon as you commence a transition, the 'to view controller' gets initialized and it's viewDidLoad gets called. 一旦开始转换,“查看控制器”就会被初始化,并且会调用viewDidLoad。 So in your transition animation code, you can grab the image you screenshotted (or the imageView) by referring to the view controller's property and do whatever you want with it in your transition. 因此,在过渡动画代码中,您可以通过参考视图控制器的属性来获取截屏图像(或imageView),并在转换过程中随意执行任何操作。 Your delays will not be disengaged. 您的延误不会被解除。

Main Point: Don't screenshot stuff during the transition. 要点:不要在过渡期间截取内容。 Instead, put the screenshot code in the viewDidLoad of the view controller, and store its output in an instance variable and refer to that in your transition. 相反,将屏幕截图代码放在视图控制器的viewDidLoad中,并将其输出存储在实例变量中,并在转换中引用它。

Hope this helps, I only just came across this problem today and just came across a solution. 希望这会有所帮助,我今天才刚刚遇到这个问题并且遇到了一个解决方案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM