《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
譯者:戴倉薯