具有同步计数线程的 UIView 动画

UIView Animation with Simultaneous Counting Thread

我正在尝试使用动画块来扩展 UIView,效果非常好。但是,我希望 UILabel 从 0 开始,每 0.01 秒加 1,直到达到 100。 我在动画之后创建了一个线程来完成这个并且它有效但是它导致我设置的动画什么都不做。 我尝试过很多不同的东西,但没有运气。实现此目标的最佳方法是什么?

我最简单的尝试,结果与所有其他尝试相同:

[UIView animateWithDuration:1 animations:^{
    _lView.frame = CGRectMake(_lView.frame.origin.x,_lView.frame.origin.y+_lView.frame.size.height,_lView.frame.size.width,-500);
}];

[[[NSThread alloc]initWithTarget:self selector:@selector(startcounting) object:nil]start];


-(void)startcounting{
for(int x=0; x<100; x++){
    [NSThread sleepForTimeInterval:0.01];
    ++_mcount;
    dispatch_async(dispatch_get_main_queue(), ^{
        _cLabel.text = [NSString stringWithFormat:@"%i",_mcount];
    });
  }
}

有两个问题:

  1. 关于同时更改标签的帧动画,问题是更改标签的文本会导致重新应用约束。正确的解决方案是不通过更改帧来设置动画,而是通过更改指示帧的约束然后在动画块中调用 layoutIfNeeded 来设置动画。参见 Animating an image view to slide upwards

  2. 关于标签的动画:

    • 您无法保证可以如此快速地处理更新。 (事实上​​ ,您应该计划永远不会超过 60 fps。)

    • 即使您能够充分降低更新的频率,您也无法保证它们会以恒定的速率进行处理(其他的东西总是会阻塞主线程一段时间毫秒,产生不一致的数字递增)。

    因此,您应该改用计时器(或者更好的 "display link",它类似于计时器,但在屏幕准备好刷新时协调调用),计算经过的时间(即计数器不会受到其他事情影响的方式,但该值将从 0 到 100 的速度足够快,足以产生您正在寻找的效果),并相应地更新标签。例如:

    @interface ViewController ()
    
    @property (nonatomic, strong) CADisplayLink *displayLink;
    @property (nonatomic) CFTimeInterval startTime;
    
    @property (weak, nonatomic) IBOutlet UILabel *label;
    
    @end
    
    @implementation ViewController
    
    - (void)startDisplayLink {
        self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)];
        self.startTime = CACurrentMediaTime();
        [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    }
    
    - (void)stopDisplayLink {
        [self.displayLink invalidate];
        self.displayLink = nil;
    }
    
    - (void)handleDisplayLink:(CADisplayLink *)displayLink {
        CFTimeInterval elapsed = CACurrentMediaTime() - self.startTime;
    
        if (elapsed < 1) {
            self.label.text = [NSString stringWithFormat:@"%.0f", elapsed * 100.0];
        } else {
            self.label.text = @"100";
            [self stopDisplayLink];
        }
    }
    

因此,结合这两点,只需调整约束(在将出口添加到约束之后)并调用 startDisplayLink(从主线程),您的标签将在整个过程中从 0 更新到 100视图动画时一秒的跨度:

[self startDisplayLink];
self.topConstraint.constant += self.lView.frame.size.height;
[UIView animateWithDuration:1.0 animations:^{
    [self.view layoutIfNeeded];
}];
_lView.frame = CGRectMake(_lView.frame.origin.x,[[UIScreen mainScreen] bounds].size.height,_lView.frame.size.width,_lView.frame.size.height);    
[[[NSThread alloc]initWithTarget:self selector:@selector(startcounting) object:nil]start];


/*
Comment this code

[UIView animateWithDuration:1 animations:^{
_lView.frame = CGRectMake(_lView.frame.origin.x,_lView.frame.origin.y+_lView.frame.size.height,_lView.frame.size.width,-500);
}];
*/

修改此方法

-(void)startcounting{
int initialY = _lView.frame.origin.y;
for(int x=0; x<=100; x++){
    [NSThread sleepForTimeInterval:0.01];
    dispatch_async(dispatch_get_main_queue(), ^{
        if (initialY>=_lView.frame.origin.y-(x*0.03))//adjust 0.03 as you required
        {
            _lView.frame = CGRectMake(_lView.frame.origin.x,_lView.frame.origin.y-(x*0.03),_lView.frame.size.width,_lView.frame.size.height);//adjust 0.03 as you required
        }
        else
        {
            _lView.frame = CGRectMake(_lView.frame.origin.x,initialY,_lView.frame.size.width,_lView.frame.size.height);
        }
       _cLabel.text = [NSString stringWithFormat:@"%i",x];
    });
}
}

我终于明白了。我以前应该看过这个。

首先,我认为这是约束的问题,所以我从正在更新的 UIView 中删除了约束。这是必要的,但没有解决问题。

整个视图控制器仍然有其自身的约束,我不能在需要时删除它们。所以我所做的是添加一个带有约束的新 UIView,并将 UIView 放在这个新 UIView 上进行动画处理。

这解决了问题,因为动画 UIView 不再负责任何约束或受任何其他约束的影响。

我仍然不明白的是,当涉及约束时,同时更新 UILabel 如何导致动画失败,但在删除 UILabel 代码后如何工作得很好。但是,尽管这确实解决了问题。