You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

UIView动画完成回调执行顺序异常原因及优化方案咨询

嗨,我来帮你拆解这个UIKit动画的问题~

1. 为什么第一个动画的完成回调会最后执行?

UIKit的动画本质是把任务添加到主队列异步执行的,而动画的完成回调会绑定原动画的时长——只要原动画被触发,不管中途有没有新动画打断它,回调都会在原动画的时长到期后,被加入主队列等待执行。

具体到你的场景:

  • 当你调用setMyButtonsVisible:NO后立刻调用setMyButtonsVisible:YES,新的show动画会直接中断当前的hide动画,UIKit会让按钮的alpha立刻跳转到hide动画的当前中间状态(比如执行了0.05秒时的0.5),然后从这个状态开始执行show动画。
  • 但之前hide动画的完成回调并没有被取消,它会在自己原本的动画时长(比如0.1秒)结束后,依然执行——哪怕此时show动画已经在运行甚至完成。这就导致hide的回调最后执行,把按钮又设为hidden=YES,覆盖了show动画的效果。

简单来说:UIView的动画回调是和原动画绑定的,不是和视图的最终状态绑定的。

2. 更优的丢弃重叠动画的方案

这里有几种成熟的解决方案,从简单到进阶:

方案一:取消旧动画并同步当前状态(兼容所有iOS版本)

在执行新动画前,先取消按钮上的所有旧动画,同步视图属性到当前实际显示状态,避免旧回调干扰。修改后的代码如下:

// 先给ViewController加一个属性记录目标状态
@property (nonatomic, assign) BOOL myButtonsVisible;

- (void)setMyButtonsVisible:(BOOL)visible {
    _myButtonsVisible = visible;
    
    // 1. 取消按钮上的所有残留动画
    [_btn1.layer removeAllAnimations];
    [_btn2.layer removeAllAnimations];
    
    // 2. 同步alpha到当前实际显示的状态(从presentationLayer获取实时值)
    _btn1.alpha = [(NSNumber *)[_btn1.layer.presentationLayer valueForKey:@"alpha"] floatValue];
    _btn2.alpha = [(NSNumber *)[_btn2.layer.presentationLayer valueForKey:@"alpha"] floatValue];
    
    if( visible ) {
        _btn1.hidden = NO;
        _btn2.hidden = NO;
        [UIView animateWithDuration:0.2 animations:^{
            _btn1.alpha = 1.0;
            _btn2.alpha = 1.0;
        }];
    } else {
        [UIView animateWithDuration:0.2 animations:^{
            _btn1.alpha = 0.0;
            _btn2.alpha = 0.0;
        } completion:^(BOOL finished) {
            // 校验当前目标状态,避免旧回调误操作
            if (!self.myButtonsVisible) {
                _btn1.hidden = YES;
                _btn2.hidden = YES;
            }
        }];
    }
}

方案二:使用UIViewPropertyAnimator(iOS 10+)

UIViewPropertyAnimator是苹果推出的更灵活的动画控制器,支持暂停、取消、重定向动画,能从根源上避免回调冲突。

先添加一个属性保存当前动画器:

@property (nonatomic, strong) UIViewPropertyAnimator *currentAnimator;
@property (nonatomic, assign) BOOL myButtonsVisible;

然后修改方法:

- (void)setMyButtonsVisible:(BOOL)visible {
    _myButtonsVisible = visible;
    
    // 1. 取消正在运行的旧动画
    if (self.currentAnimator && self.currentAnimator.isRunning) {
        [self.currentAnimator stopAnimation:YES];
        [self.currentAnimator finishAnimationAtPosition:UIViewAnimatingPositionCurrent];
    }
    
    if (visible) {
        _btn1.hidden = NO;
        _btn2.hidden = NO;
        
        self.currentAnimator = [UIViewPropertyAnimator runningPropertyAnimatorWithDuration:0.2 delay:0 options:0 animations:^{
            _btn1.alpha = 1.0;
            _btn2.alpha = 1.0;
        } completion:^(UIViewAnimatingPosition finalPosition) {
            self.currentAnimator = nil;
        }];
    } else {
        self.currentAnimator = [UIViewPropertyAnimator runningPropertyAnimatorWithDuration:0.2 delay:0 options:0 animations:^{
            _btn1.alpha = 0.0;
            _btn2.alpha = 0.0;
        } completion:^(UIViewAnimatingPosition finalPosition) {
            if (finalPosition == UIViewAnimatingPositionEnd && !self.myButtonsVisible) {
                _btn1.hidden = YES;
                _btn2.hidden = YES;
            }
            self.currentAnimator = nil;
        }];
    }
}

这种方法可以精准控制动画生命周期,旧动画被取消后,它的回调不会再执行。

方案三:优化临时修复(状态校验)

如果你不想改动太多现有代码,可以把状态校验做得更规范:

@property (nonatomic, assign) BOOL myButtonsVisible;

- (void)setMyButtonsVisible:(BOOL)visible {
    _myButtonsVisible = visible; // 先更新目标状态
    
    if( visible ) {
        _btn1.hidden = NO;
        _btn2.hidden = NO;
        [UIView animateWithDuration:0.2 animations:^{
            _btn1.alpha = 1.0;
            _btn2.alpha = 1.0;
        }];
    } else {
        [UIView animateWithDuration:0.2 animations:^{
            _btn1.alpha = 0.0;
            _btn2.alpha = 0.0;
        } completion:^(BOOL finished) {
            // 只有当前目标状态确实是隐藏,才执行操作
            if (!self.myButtonsVisible) {
                _btn1.hidden = YES;
                _btn2.hidden = YES;
            }
        }];
    }
}

这个方法简单易行,通过状态校验避免错误的回调操作。

内容的提问来源于stack exchange,提问作者olha

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 07:51:23