UIViewPropertyAnimator对象有三个属性:
isRunning:只读属性,表明动画是否处于运动状态,默认值为false,当调用startAnimation()时变为true,如果动画暂停(paused)、停止(stopped)、完成(finish),它将再次变为false。
isReversed:可读可写属性,默认值为false,当设置为true时,动画将反转执行到初始状态。
state:只读属性,默认值为inactive,这意味着你刚刚创建动画还没有调用任何方法。
调用以下方法会变成active状态:
调用startAnimation()来启动动画;
在没有开始动画的情况下调用pauseAnimation();
设置fractionComplete属性将动画“倒回”到某个位置。
动画完成后,state将切换回inactive状态。如果你调用stopAnimation(),state属性将变成stopped状态,在这种状态下,你唯一能做的就是停止整个动画,调用finishAnimation(at:)将动画变回inactive状态。
UIViewPropertyAnimator只能按照特定的顺序切换状态,你不能从inactive切换到stopped,也不能从stopped切换到active。
UIViewPropertyAnimator在iOS11之后有一个新的属性pausesOnCompletion,当设置为true时,一旦动画运行完成,它将不自动停止而是进入暂停状态,这时候你将可以从暂停状态继续做你后续的工作。
流程图如下
3D touch交互动画(iOS10之后)
3D touch交互主要使用UIPreviewInteractionDelegate协议的方法,这里不介绍3D touch如何使用,仅仅介绍3D touch动画。
public func previewInteractionDidCancel(_ previewInteraction: UIPreviewInteraction) {
owner.cancelPreview()
}
func previewInteractionShouldBegin(_ previewInteraction: UIPreviewInteraction) -> Bool {
if let indexPath = collectionView?.indexPathForItem(at: previewInteraction.location(in: collectionView!)),
let cell = collectionView?.cellForItem(at: indexPath) as? IconCell {
owner.startPreview(for: cell.icon)
}
return true
}
func previewInteraction(_ previewInteraction: UIPreviewInteraction, didUpdatePreviewTransition transitionProgress: CGFloat, ended: Bool) {
owner.updatePreview(percent: transitionProgress)
if ended {
owner.finishPreview()
}
}
我们将在应用内实现这种效果:
在AnimatorFactory.swift中创建动画:
static func grow(view: UIVisualEffectView, blurView: UIVisualEffectView) -> UIViewPropertyAnimator {
view.contentView.alpha = 0
view.transform = .identity
let animator = UIViewPropertyAnimator(duration: 0.5, curve: .easeIn)
animator.addAnimations {
blurView.effect = UIBlurEffect(style: UIBlurEffectStyle.dark)
view.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
}
animator.addCompletion { (_) in
blurView.effect = UIBlurEffect(style: UIBlurEffectStyle.dark)
}
return animator
}
在LockScreenViewController.swift中添加属性:
var startFrame: CGRect?//动画的初始坐标
var previewView: UIView?//icon的快照,用于3D touch显示
var previewAnimator: UIViewPropertyAnimator?//动画
let previewEffectView = IconEffectView(blur: .extraLight)//重按后icon的边框边框
let blurView = UIVisualEffectView(effect: nil)//3D touch后的背景模糊效果
3D touch开始时,设置属性值:
func startPreview(for forView: UIView) {
previewView?.removeFromSuperview()//移除旧的icon快照
previewView = forView.snapshotView(afterScreenUpdates: false)
view.insertSubview(previewView!, aboveSubview: blurView)//
previewView?.frame = forView.convert(forView.bounds, to: view)//设置快照的坐标在原icon之上
startFrame = previewView?.frame//记住初始坐标
addEffectView(below: previewView!)//将icon边框效果插入icon快照下面
previewAnimator = AnimatorFactory.grow(view: previewEffectView, blurView: blurView)//创建动画,动画还未开始
}
func addEffectView(below forView: UIView) {
previewEffectView.removeFromSuperview()
previewEffectView.frame = forView.frame
forView.superview?.insertSubview(previewEffectView, belowSubview: forView)
}
3D touch更新回调后更新动画到指定位置:
func updatePreview(percent: CGFloat) {
previewAnimator?.fractionComplete = max(0.01, min(0.99, percent))
}
下面我们来实现动画的完成和取消方法。
继续在AnimatorFactory.swift中添加动画:
static func reset(frame: CGRect, view: UIVisualEffectView, blurView: UIVisualEffectView) -> UIViewPropertyAnimator {//还原
return UIViewPropertyAnimator(duration: 0.5, dampingRatio: 0.7, animations: {
view.transform = .identity
view.frame = frame
view.contentView.alpha = 0
blurView.effect = nil
})
}
在LockScreenViewController中实现3D touch还原的动画:
func cancelPreview() {//用户轻按时还原动画
if let previewAnimator = previewAnimator {
previewAnimator.isReversed = true
previewAnimator.startAnimation()
}
}
还原的动画就完成啦,下面添加完成的动画。
在AnimatorFactory.swift中的grow方法,我们已经添加了完成的block,我们只需对其进行修改即可:
animator.addCompletion { position in
switch position {
case .start:
blurView.effect = nil
case .end:
blurView.effect = UIBlurEffect(style: .dark)
default: break
}
}
position是一个UIViewAnimatingPosition类型的枚举值,分别代表动画是在开始停止,结束后停止,还是当前位置停止。 通常你都会收到结束的枚举值。它有三个枚举值:start、end、current,如果你的动画自然完成或者以其他方式结束,它将返回end值;如果你反转动画并在开始的位置完成,那么它将返回start;如果你中途停止动画并在那里完成,它将返回current值。
动画在开始位置完成(即轻按3D touch)后需要移除icon快照和边框,在LockScreenViewController的cancelPreview方法中添加代码:
previewAnimator.addCompletion { (position) in
switch position {
case .start:
self.previewView?.removeFromSuperview()
self.previewEffectView.removeFromSuperview()
default: break
}
}
addCompletion可以添加多个,并不会对之前添加的有任何影响。
3D touch的完成和还原动画就实现啦。下面我们来设置3D touch弹出的菜单(即之前添加的icon快照的边框previewEffectView,我们只需要改变其位置和大小即可)。
在AnimatorFactory中添加动画:
static func complete(view: UIVisualEffectView) -> UIViewPropertyAnimator {
return UIViewPropertyAnimator(duration: 0.3, dampingRatio: 0.7, animations: {
view.contentView.alpha = 1
view.transform = .identity
view.frame = CGRect(x: view.frame.minX - view.frame.minX/2.5, y: view.frame.maxY - 140, width: view.frame.width + 120, height: 60)
})
}
在LockScreenViewController中3D touch完成的回调里添加动画:
func finishPreview() {
previewAnimator?.stopAnimation(false)//false表示将动画停止,需要手动调用finishAnimation完成动画,true表示动画停止,并将动画置于inactive状态,不需要调用finishAnimation
previewAnimator?.finishAnimation(at: .end)
previewAnimator = nil
AnimatorFactory.complete(view: previewEffectView).startAnimation()
}
下面添加点击背景取消3D touch的动画。
在finishPreview中添加代码:
blurView.effect = UIBlurEffect(style: .dark)
blurView.isUserInteractionEnabled = true
blurView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(dismissMenu)))
设置dimissMenu的动画:
@objc func dismissMenu() {
let reset = AnimatorFactory.reset(frame: startFrame!, view: previewEffectView, blurView: blurView)
reset.addCompletion { (_) in
self.previewEffectView.removeFromSuperview()
self.previewView?.removeFromSuperview()
self.blurView .isUserInteractionEnabled = false
}
reset.startAnimation()
}
运行效果: