我想使用按钮作为切换 - 点击一次&图像无限旋转。再次单击,图像停止,再次单击,重新启动。
我发现这个答案有助于让动画继续下去: Rotate a view for 360 degrees indefinitely in Swift?
但是,我不清楚如何制止事情。我已经实施了以下代码&它似乎工作,但我很好奇,如果这是停止动画的正确方法,或者是否有另一种首选方法。另外 - 我的轮换一直持续到完成,但是我想知道是否可以在按下按钮时冻结位置的旋转(我在下面的第二次尝试中尝试过.removeAllAnimations(),但是没有#39似乎完全没用。
@IBOutlet weak var imageView: UIImageView!
var stopRotation = true
func rotateView(targetView: UIView, duration: Double = 1.0) {
if !stopRotation {
UIView.animate(withDuration: duration, delay: 0.0, options: .curveLinear, animations: {
targetView.transform = targetView.transform.rotated(by: CGFloat(Double.pi))
}) { finished in
self.rotateView(targetView: targetView, duration: duration)
}
}
}
@IBAction func spinPressed(_ sender: UIButton) {
stopRotation = !stopRotation
if !stopRotation {
rotateView(targetView: imageView)
}
}
这确实有效。我也想知道是否可以在旋转中停止动画。它的设置方式,动画在停止前完全旋转180度。我还尝试在spinPressed动作中添加removeAnimation,并在rotateView中删除stopRotation检查,但这似乎不起作用 - 旋转继续&如果再次按下spinPressed,则速度会更快(见下文):
@IBOutlet weak var imageView: UIImageView!
var stopRotation = true
func rotateView(targetView: UIView, duration: Double = 1.0) {
UIView.animate(withDuration: duration, delay: 0.0, options: .curveLinear, animations: {
targetView.transform = targetView.transform.rotated(by: CGFloat(Double.pi))
}) { finished in
self.rotateView(targetView: targetView, duration: duration)
}
}
@IBAction func spinPressed(_ sender: UIButton) {
stopRotation = !stopRotation
if stopRotation {
imageView.layer.removeAllAnimations()
} else {
rotateView(targetView: imageView)
}
}
确认第一种方法是否合理是值得欢迎的。如果有一种方法可以在旋转中间停止旋转,那么这也是受欢迎的(同时也让我直截了当地看待我对removeAllAnimations的错误思考)。
谢谢! JG
答案 0 :(得分:4)
有几种方法可以解决您的问题:
如果支持iOS 10+,您可以使用UIViewPropertyAnimator
,其动画可以暂停并重新启动(从暂停的位置恢复):
private var animator: UIViewPropertyAnimator?
@IBAction func didTapButton(_ sender: Any) {
guard let animator = animator else {
createAnimation()
return
}
if animator.isRunning {
animator.pauseAnimation()
} else {
animator.startAnimation()
}
}
/// Create and start 360 degree animation
///
/// This will fire off another animation when one 360° rotation finishes.
private func createAnimation() {
animator = UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 4, delay: 0, options: .curveLinear, animations: {
UIView.animateKeyframes(withDuration: 4, delay: 0, animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1.0 / 3.0) {
self.animatedView.transform = .init(rotationAngle: .pi * 2 * 1 / 3)
}
UIView.addKeyframe(withRelativeStartTime: 1.0 / 3.0, relativeDuration: 1.0 / 3.0) {
self.animatedView.transform = .init(rotationAngle: .pi * 2 * 2 / 3)
}
UIView.addKeyframe(withRelativeStartTime: 2.0 / 3.0, relativeDuration: 1.0 / 3.0) {
self.animatedView.transform = .identity
}
})
}, completion: { [weak self] _ in
self?.createAnimation()
})
}
您也可以使用UIKit Dynamics旋转项目。然后,您可以移除正在执行轮换的UIDynamicItemBehavior
,它只会停在原来的位置。它会自动离开视图transform
。然后,要恢复旋转,只需再次为旋转添加UIDynamicItemBehavior
:
private lazy var animator: UIDynamicAnimator = UIDynamicAnimator(referenceView: view)
private var rotate: UIDynamicItemBehavior?
@IBAction func didTapButton(_ sender: Any) {
if let rotate = rotate {
animator.removeBehavior(rotate)
self.rotate = nil
} else {
rotate = UIDynamicItemBehavior(items: [animatedView])
rotate?.allowsRotation = true
rotate?.angularResistance = 0
rotate?.addAngularVelocity(1, for: animatedView)
animator.addBehavior(rotate!)
}
}
这不能让你轻松控制旋转的速度,而是由angularVelocity
决定,但它是一个很好的简单方法(并支持iOS 7.0及更高版本)。
用于停止动画并将其停在停止位置的老派方法是捕捉动画的presentationLayer
(显示它在飞行途中的位置)。然后,您可以获取当前状态,停止动画,并将转换设置为presentationLayer
报告的内容。
private var isAnimating = false
@IBAction func didTapButton(_ sender: Any) {
if isAnimating {
let transform = animatedView.layer.presentation()!.transform
animatedView.layer.removeAllAnimations()
animatedView.layer.transform = transform
} else {
let rotate = CABasicAnimation(keyPath: "transform.rotation")
rotate.byValue = 2 * CGFloat.pi
rotate.duration = 4
rotate.repeatCount = .greatestFiniteMagnitude
animatedView.layer.add(rotate, forKey: nil)
}
isAnimating = !isAnimating
}
如果要使用基于UIView
块的动画,则必须捕获停止动画的角度,以便知道从何处重新开始动画。诀窍是抓住m12
的{{1}}和m11
:
CATransform3D
因此,这会产生:
angle = atan2(transform.m12, transform.m11)
您可以使用private var angle: CGFloat = 0
private var isAnimating = false
@IBAction func didTapButton(_ sender: Any) {
if isAnimating {
let transform = animatedView.layer.presentation()!.transform
angle = atan2(transform.m12, transform.m11)
animatedView.layer.removeAllAnimations()
animatedView.layer.transform = transform
} else {
UIView.animate(withDuration: 4, delay: 0, options: .curveLinear, animations: {
UIView.animateKeyframes(withDuration: 4, delay: 0, options: .repeat, animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1.0 / 3.0) {
self.animatedView.transform = .init(rotationAngle: self.angle + .pi * 2 * 1 / 3)
}
UIView.addKeyframe(withRelativeStartTime: 1.0 / 3.0, relativeDuration: 1.0 / 3.0) {
self.animatedView.transform = .init(rotationAngle: self.angle + .pi * 2 * 2 / 3)
}
UIView.addKeyframe(withRelativeStartTime: 2.0 / 3.0, relativeDuration: 1.0 / 3.0) {
self.animatedView.transform = .init(rotationAngle: self.angle)
}
})
})
}
isAnimating = !isAnimating
}
自行旋转对象,将角度更新为某个计算值。然后停止旋转就像使显示链接无效一样简单,从而将其留在停止时的位置。然后,您只需将显示链接添加回runloop即可恢复动画。
这种技术可以让你获得很大的控制权,但这种方法最不优雅。
答案 1 :(得分:0)
这第一种方法似乎是合理的。如果要对停止位置进行细粒度控制,则将动画持续时间和旋转弧度除以恒定量。将更频繁地调用您的完成处理程序,以检查是否应继续轮换。
let animationFrequency = 30.0
func rotateView(targetView: UIView, duration: Double = 1.0 / animationFrequency) {
UIView.animate(withDuration: duration, delay: 0.0, options: .curveLinear, animations: {
targetView.transform = targetView.transform.rotated(by: CGFloat(Double.pi / animationFrequency))
}) { finished in
self.rotateView(targetView: targetView, duration: duration)
}
}
答案 2 :(得分:0)
我有一个无限的"翻转"标签(它是一个水印),我需要"关闭"显示共享选项时的一个特定标签(或水印)。我通过计时器发生了所有这些 - 在翻转之前保持特定标签观看几秒钟。当你这样做时,关闭计时器很简单。
public class FlipView:UIView {
private var labelWatermark:FlipLabel!
private var labelTap:FlipLabel!
let transitionOptions: UIViewAnimationOptions = [.transitionFlipFromRight, .showHideTransitionViews]
var isLabel1 = true
var timer:Timer!
convenience public init(appName:String) {
self.init(frame: CGRect.zero)
labelTap = FlipLabel("Tap to remove", "Watermark")
self.addSubview(labelTap)
labelWatermark = FlipLabel("created with", appName)
self.addSubview(labelWatermark)
timer = Timer.scheduledTimer(
timeInterval: 3.0, target: self, selector: #selector(flipViews),
userInfo: nil, repeats: true)
timer.fire()
}
internal func startFlip() {
timer = Timer.scheduledTimer(
timeInterval: 3.0, target: self, selector: #selector(flipViews),
userInfo: nil, repeats: true)
timer.fire()
}
internal func stopFlip() {
timer.invalidate()
UIView.transition(from: labelTap, to: labelWatermark, duration: 1, options: transitionOptions, completion: nil)
isLabel1 = true
}
@objc func flipViews() {
if (isLabel1) {
UIView.transition(from: labelWatermark, to: labelTap, duration: 1, options: transitionOptions, completion: nil)
isLabel1 = false
} else {
UIView.transition(from: labelTap, to: labelWatermark, duration: 1, options: transitionOptions, completion: nil)
isLabel1 = true
}
}
}