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()
}
運行效果: