iOS如何简洁实现UIImageView指定次数左右旋转重复动画
问题分析
你当前嵌套UIView.animate的实现有两个核心问题:
- 多层闭包嵌套可读性极差,后续修改旋转次数、角度的维护成本很高
- 如果你的
rotate(angle:)方法是在当前transform基础上做增量旋转,会出现角度累加误差,只能靠手动试错凑最终角度复位,稳定性极差,系统版本、动画选项变化都可能导致最终位置偏移。
推荐实现方案
不需要嵌套闭包,也不需要手动凑复位角度,直接用关键帧动画定义所有旋转节点即可,动画节奏完全可控,结束后自动回到无旋转状态。
方案1:CAKeyframeAnimation(最稳定,性能最好)
直接针对layer的旋转属性做关键帧动画,动画执行完毕后自动移除,保持视图初始的无旋转状态,不会有任何状态残留:
// 先明确设置初始状态为无旋转 imageView.transform = .identity let rotateAnimation = CAKeyframeAnimation(keyPath: "transform.rotation.z") let singleAnimateDuration = 0.3 // 单段旋转时长,和你原有实现一致 let maxRotateAngle = Double.pi / 8 // 最大旋转角度,按需求设置 // 按顺序定义所有关键帧的旋转角度:初始->左偏->右偏 往返3次,最终回到0 rotateAnimation.values = [ 0, maxRotateAngle, -maxRotateAngle, maxRotateAngle, -maxRotateAngle, maxRotateAngle, -maxRotateAngle, 0 ] // 按单段时长计算每个关键帧的时间节点 let totalDuration = singleAnimateDuration * Double(rotateAnimation.values!.count - 1) rotateAnimation.keyTimes = rotateAnimation.values?.enumerated().map { index, _ in NSNumber(value: Double(index) * singleAnimateDuration / totalDuration) } rotateAnimation.duration = totalDuration rotateAnimation.beginTime = CACurrentMediaTime() + 0.4 // 对应你需要的0.4s初始延迟 rotateAnimation.isRemovedOnCompletion = true imageView.layer.add(rotateAnimation, forKey: "leftRightShake")
这个方案的优势:
- 所有节点明确定义,要修改往返次数只需要在
values数组里增减maxRotateAngle和-maxRotateAngle的成对节点即可,最后保持0为终点 - 完全没有角度累加问题,动画结束后视图自动回到初始的无旋转状态,不需要任何试错调参
- 性能高于UIView级别的动画,不会受视图其他布局更新影响
方案2:UIView关键帧动画(如果更习惯用UIView动画API)
如果不想直接操作CoreAnimation,可以用UIView内置的关键帧API,同样不需要嵌套闭包:
imageView.transform = .identity let singleAnimateDuration = 0.3 let maxRotateAngle = Double.pi / 8 let totalDuration = singleAnimateDuration * 7 // 共7段动画,总时长2.1s UIView.animateKeyframes(withDuration: totalDuration, delay: 0.4, options: [], animations: { let relativeDuration = singleAnimateDuration / totalDuration // 第1段:0 -> 左偏最大角度 UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: relativeDuration) { self.imageView.transform = CGAffineTransform(rotationAngle: maxRotateAngle) } // 第2段:左偏 -> 右偏最大角度 UIView.addKeyframe(withRelativeStartTime: relativeDuration, relativeDuration: relativeDuration) { self.imageView.transform = CGAffineTransform(rotationAngle: -maxRotateAngle) } // 第3段:右偏 -> 左偏 UIView.addKeyframe(withRelativeStartTime: 2*relativeDuration, relativeDuration: relativeDuration) { self.imageView.transform = CGAffineTransform(rotationAngle: maxRotateAngle) } // 第4段:左偏 -> 右偏 UIView.addKeyframe(withRelativeStartTime: 3*relativeDuration, relativeDuration: relativeDuration) { self.imageView.transform = CGAffineTransform(rotationAngle: -maxRotateAngle) } // 第5段:右偏 -> 左偏 UIView.addKeyframe(withRelativeStartTime: 4*relativeDuration, relativeDuration: relativeDuration) { self.imageView.transform = CGAffineTransform(rotationAngle: maxRotateAngle) } // 第6段:左偏 -> 右偏 UIView.addKeyframe(withRelativeStartTime: 5*relativeDuration, relativeDuration: relativeDuration) { self.imageView.transform = CGAffineTransform(rotationAngle: -maxRotateAngle) } // 第7段:右偏 -> 复位到0 UIView.addKeyframe(withRelativeStartTime: 6*relativeDuration, relativeDuration: relativeDuration) { self.imageView.transform = .identity } })
注意事项
如果你的自定义rotate(angle:)方法是在当前transform基础上做增量旋转(也就是每次调用都会在现有角度上叠加新角度),建议直接给transform属性赋值设置绝对角度,避免累加误差导致的位置偏移。
内容的提问来源于stack exchange,提问作者paprika
相关产品推荐
相关产品推荐

