《iOS 10 day by day》是 shinobicontrols 公司編寫的系列博客,介紹開發者須要瞭解的 iOS 10 新特性,每週更新。本系列翻譯(文集地址)已取得官方受權。目錄點此。倉薯翻譯,歡迎指正:)ios
Shinobicontrols 爲 iOS 和 Android 開發者提供高性能、響應式的 UI 控件 SDK,尤爲是圖表方面的控件。 官網 : shinobicontrols.com twitter : @shinobicontrolsgit
用基於 block 的 UIView animation 來編寫 view 屬性(frame, transform 等等)變化的動畫很是簡單。只須要短短几行代碼:github
view.alpha = 1
UIView.animate(withDuration: 2) {
containerView.alpha = 0
}複製代碼
你能夠指定動畫結束以後調用的 completion block。若是默認的勻速動畫不能知足你的要求,還能夠調整時間曲線。閉包
可是,若是你須要一種自定義的曲線動畫,相應的屬性變化首先要快速開始,而後再急速慢下來,該怎麼辦呢?另一個有點麻煩的問題是,怎麼取消正在進行中的動畫?雖然這些問題均可以解決,用第三方庫或者建立一個新的 animation 來取代進行中的 animation。但蘋果在 UIKit 中新加的組件能把這些步驟簡化許多:進入UIViewPropertyAnimator
的世界吧!app
UIViewPropertyAnimator
的 API 設計得很完善,可擴展性也很好。它 cover 了傳統 UIView animation 動畫的絕大部分功能,而且大大加強了你對動畫過程的掌控能力。具體來講,你能夠在動畫過程當中任意時刻暫停,能夠隨後再選擇繼續,甚至還能在動畫過程當中動態改變更畫的屬性(例如,原本動畫終點在屏幕左下角的,能夠在動畫過程當中把終點改到右上角)。iview
爲了探索這個新的類,咱們來看幾個例子,這幾個例子都是演示一張圖片劃過屏幕的動畫。如同全部 Day by Day 系列的文章,例子的代碼能夠在 Github 上下載到。此次咱們用的是 Playground。編輯器
咱們全部的 playground 頁面都是讓一個小忍者劃過屏幕的動畫。爲了方便對比這些頁面的代碼,咱們把公共部分的代碼藏在 Sources
文件夾裏。這樣不只能簡化每一個頁面的代碼,還能加快編譯過程,由於 Sources
裏的代碼是預編譯過的。ide
Sources
裏包含一個簡單的UIView
子類,叫作NinjaContainerView
。它的惟一功能就是添加一個 UIImageView
做爲子 view,來顯示咱們的小忍者。我把忍者圖片加到了 Resources
裏。函數
import UIKit
public class NinjaContainerView: UIView {
public let ninja: UIImageView = {
let image = UIImage(named: "ninja")
let view = UIImageView(image: image)
view.frame = CGRect(x: 0, y: 0, width: 45, height: 39)
return view
}()
public override init(frame: CGRect) {
// Animating view
super.init(frame: frame)
// Position ninja in the bottom left of the view
ninja.center = {
let x = (frame.minX + ninja.frame.width / 2)
let y = (frame.maxY - ninja.frame.height / 2)
return CGPoint(x: x, y: y)
}()
// Add image to the container
addSubview(ninja)
backgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
/// Moves the ninja view to the bottom right of its container, positioned just inside.
public func moveNinjaToBottomRight() {
ninja.center = {
let x = (frame.maxX - ninja.frame.width / 2)
let y = (frame.maxY - ninja.frame.height / 2)
return CGPoint(x: x, y: y)
}()
}
}複製代碼
如今,在每一個 playground 頁面裏,咱們能夠複製粘貼如下代碼:工具
import UIKit
import PlaygroundSupport
// Container for our animating view
let containerView = NinjaContainerView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))
let ninja = containerView.ninja
// Show the container view in the Assistant Editor
PlaygroundPage.current.liveView = containerView複製代碼
這樣咱們就能夠用上 Playground 強大的 "Live View" 功能,不用啓動 iOS 模擬器就能夠展現動畫效果。儘管 Playground 仍是有些很差用的地方,但用來嘗試新功能是很是合適的。
要顯示 Live View,點擊菜單欄上的 View -> Assistant Editor -> Show Assistant Editor,或者點擊右上角工具欄裏兩環相套的圖標。若是在右半邊的編輯器裏沒有看到 live view,要確保選中的是 Timeline 而不是 Manual —— 不得不認可我在這裏浪費了一點時間。
UIViewPropertyAnimator
的用法能夠跟傳統的 animation block 同樣:
UIViewPropertyAnimator(duration: 1, curve: .easeInOut) {
containerView.moveNinjaToBottomRight()
}.startAnimation()複製代碼
這會觸發一個時長爲 1 秒,時間曲線是緩進緩出的動畫。動畫的內容是閉包裏的部分。
注意咱們是經過調用 startAnimation()
來顯式啓動動畫的。另一種建立 animator 的方法能夠不用手動啓動動畫,就是 runningPropertyAnimator(withDuration:delay:options:animations:completion:)
。確實有點長,因此可能還不如用第一種。
先建立好 animator ,再往上添加動畫也很容易:
// view 設置好以後,咱們先來一個簡單的動畫
let animator = UIViewPropertyAnimator(duration: 1, curve: .easeInOut)
// 添加第一個 animation block
animator.addAnimations {
containerView.moveNinjaToBottomRight()
}
// 而後再加第二個
animator.addAnimations {
ninja.alpha = 0
}複製代碼
這兩個 animation block 會同時進行。
添加 completion block 的方法也很相似:
animator.addCompletion {
_ in
print("Animation completed")
}
animator.addCompletion {
position in
switch position {
case .end: print("Completion handler called at end of animation")
case .current: print("Completion handler called mid-way through animation")
case .start: print("Completion handler called at start of animation")
}
}複製代碼
若是動畫完整跑完的話,咱們能夠在控制檯看到如下信息:
Animation completed
Completion handler called at end of animation複製代碼
咱們能夠利用 animator 讓動畫跟隨拖拽的進度進行:
let animator = UIViewPropertyAnimator(duration: 5, curve: .easeIn)
// Add our first animation block
animator.addAnimations {
containerView.moveNinjaToBottomRight()
}
let scrubber = UISlider(frame: CGRect(x: 0, y: 0, width: containerView.frame.width, height: 50))
containerView.addSubview(scrubber)
let eventListener = EventListener()
eventListener.eventFired = {
animator.fractionComplete = CGFloat(scrubber.value)
}
scrubber.addTarget(eventListener, action: #selector(EventListener.handleEvent), for: .valueChanged)複製代碼
Playground 整體來講是很好用的,並且還能在 Live View 裏面添加可交互的 UI 控件。然而,接受響應事件就有點麻煩,由於咱們須要一個
NSObject
的子類來監聽諸如.valueChanged
這種事件。因此,咱們簡單建立一個EventListener
,一旦觸發它的handleEvent
方法,它會調用咱們的eventFired
閉包。
這裏 fractionComplete
值的計算方法跟時間沒有關係了,因此咱們的小忍者再也不像以前指定的同樣,會優雅地緩動。
Property animator 最強大的功能體如今它能隨時打斷正在進行的動畫。讓動畫反向也很是容易,只需設置 isReversed
屬性便可。
爲了演示這一點,咱們使用關鍵幀動畫,這樣就能夠製做一個多階段的動畫了:
animator.addAnimations {
UIView.animateKeyframes(withDuration: animationDuration, delay: 0, options: [.calculationModeCubic], animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5) {
ninja.center = containerView.center
}
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5) {
containerView.moveNinjaToBottomRight()
}
})
}
let button = UIButton(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 30)))
button.setTitle("Reverse", for: .normal)
button.setTitleColor(.black(), for: .normal)
button.setTitleColor(.gray(), for: .highlighted)
let listener = EventListener()
listener.eventFired = {
animator.isReversed = true
}
button.addTarget(listener, action: #selector(EventListener.handleEvent), for: .touchUpInside)
containerView.addSubview(button)
animator.startAnimation()複製代碼
按下按鈕的時候,animator 就會把動畫反向進行,只要這一時刻動畫還沒結束。
Property animator 在簡潔優美的同時,還有很強的擴展性。若是你須要在蘋果提供的時間函數以外自定義另外一種時間曲線,只需傳進一個實現 UITimingCurveProvider
協議的對象。大部分狀況下用到的是 UICubicTimingParameters
或者 UISpringTimingParameters
。
例如,咱們想讓小忍者在劃過屏幕的過程當中,先快速加速,而後再慢慢中止。以下圖的貝塞爾曲線所示(繪製曲線用了這個很方便的在線工具):
let bezierParams = UICubicTimingParameters(controlPoint1: CGPoint(x: 0.05, y: 0.95),
controlPoint2: CGPoint(x: 0.15, y: 0.95))
let animator = UIViewPropertyAnimator(duration: 4, timingParameters:bezierParams)
animator.addAnimations {
containerView.moveNinjaToBottomRight()
}
animator.startAnimation()複製代碼
新的 property animator 讓編寫動畫更簡單,它的 API 跟傳統方法相似,還添加了打斷動畫、自定義時間曲線等功能。
Apple 爲 UIViewPropertyAnimator
提供了詳盡的文檔。另外,也能夠看看這場 WWDC 視頻,深度解讀這些新的 API,還講了怎麼用新的 API 來作 viewController 跳轉的過渡動畫。另外還有一些有趣的例子,例如一些簡單的遊戲。
原文地址:iOS 10 Day by Day :: Day 4 :: UIViewPropertyAnimator
原做者:Sam Burnstone @sam_burnstone
ShinobiControls 官網:ShinobiControls.com twitter : @shinobicontrols
譯者:戴倉薯