iOS9 Day-by-Day是作者Chris Grant寫的系列博客,覆蓋了iOS開發者必須知道的關于iOS 9的新技術與API,并且還進行了實際操作演練,每篇文章中相關的代碼Chris都會將其上傳至GitHub上。在Search APIs、UI Testing、Storyboard References、UIStackView、Xcode Code Coverage、Multitasking、Contacts Framework、Apple Pay之后,作者寫到了第九篇——UIKit Dynamics。譯文如下:
UIKit Dynamics是在iOS 7中首次被介紹,可以讓開發者通過簡單的方式,給應用界面添加模擬物理世界的交互動畫。iOS 9中又加入了一些大的改進,我們將在本文中查看一些。
在iOS 9之前,UIKitDynamics的collision bounds只能是長方形。這讓一些并非是完美的長方形的碰撞效果看起來有些古怪。iOS 9中支持三種collision bounds分別是Rectangle(長方形)、Ellipse(橢圓形)和Path(路徑)。Path可以是任意路徑,只要是逆時針的,并且不是交叉在一起的。一個警告是,path必須是凸面的不能使凹面的。
為了提供一個自定義的collision bounds ,你可以子定義一個UIView的子類。
[cpp] view plaincopy
class Ellipse: UIView {
override var collisionBoundsType: UIDynamicItemCollisionBoundsType {
return .Ellipse
}
}
如果你有個自定義的視圖有一個自定義的bounds,你同樣可以這么做。
在iOS 9之前,只有一種gravity behaviour(重力感應)類型的behaviour。開發者也無法擴展或者自定義其他類型。
現在,UIKit Dynamics包含了更多的behaviours:
Linear Gravity
Radial Gravity
Noise
Custom
這些behaviours都有一些屬性可以用來設置不同的效果,并且可以簡單的添加和使用。
我們來用創建一個例子,把這兩個特性都融合進來。它有幾個視圖(一個橢圓和一個正方形)添加了一些碰撞邏輯和一些噪音的UIFieldBehavior。

要使用UIKit Dynamics,首先要創建一個UIDynamicAnimator。在viewDidLoad方法中,為你的變量創建一個引用。
[cpp] view plaincopy
// Set up a UIDynamicAnimator on the view.
animator = UIDynamicAnimator(referenceView: view)
現在你需要添加一些視圖,它們將會動起來。
[cpp] view plaincopy
// Add two views to the view
let square = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
square.backgroundColor = .blueColor()
view.addSubview(square)
let ellipse = Ellipse(frame: CGRect(x: 0, y: 100, width: 100, height: 100))
ellipse.backgroundColor = .yellowColor()
ellipse.layer.cornerRadius = 50
view.addSubview(ellipse)
這是我們給view添加的兩個基本的behaviors。
[cpp] view plaincopy
let items = [square, ellipse]
// Create some gravity so the items always fall towards the bottom.
let gravity = UIGravityBehavior(items: items)
animator.addBehavior(gravity)
個behaviors,我們添加了一個重力感應模型。
[cpp] view plaincopy
let noiseField:UIFieldBehavior = UIFieldBehavior.noiseFieldWithSmoothness(1.0, animationSpeed: 0.5)
// Set up the noise field
noiseField.addItem(square)
noiseField.addItem(ellipse)
noiseField.strength = 0.5
animator.addBehavior(noiseField)
接下來我們添加了一個UIFieldBehavior。使用noiseFieldWithSmoothness方法進行了初始化。我們把方形和橢圓形添加到了behavior中,然后給animator添加了field behavior。
[cpp] view plaincopy
// Don't let objects overlap each other - set up a collide behaviour
let collision = UICollisionBehavior(items: items)
collision.setTranslatesReferenceBoundsIntoBoundaryWithInsets(UIEdgeInsets(top: 20, left: 5, bottom: 5, right: 5))
animator.addBehavior(collision)
我們接著創建了一個UICollisionBehavior。這會阻止兩個元素在碰撞時疊加,并增加了物理模型的動畫效果。我們使用setTranslatesReferenceBoundsIntoBoundaryWithInsets,給視圖添加了一個邊緣的設置。如果不設置這個盒子的話,剛才的重力感應動畫會把方形和橢圓形的視圖掉進屏幕以下,而回不來。(我們就看不到碰撞了)
說到重力感應,我們需要確保他的方向始終是朝下的,也就是實際的物理世界中的方向。為了做到這點,我們需要使用 CoreMotion framework。創建一個CMMotionManager 變量。
[cpp] view plaincopy
let manager:CMMotionManager = CMMotionManager()
我們設置一個變量作為類的屬性,是因為我們始終需要用到它。否則的話,CMMotionManager會因為被釋放掉而無法更新。當我們發現設備的方向發生變化,為們設置重力感應模型的 gravityDirection屬性來,讓重力的方向始終向下。
[cpp] view plaincopy
// Used to alter the gravity so it always points down.
if manager.deviceMotionAvailable {
manager.deviceMotionUpdateInterval = 0.1
manager.startDeviceMotionUpdatesToQueue(NSOperationQueue.mainQueue(), withHandler:{
deviceManager, error in
gravity.gravityDirection = CGVector(dx: deviceManager!.gravity.x, dy: -deviceManager!.gravity.y)
})
}
注意,我們這個例子只支持了portrait一種模式,如果你希望支持全部的方向的話,你可以自己添加一些計算代碼。
當你打開應用時,你可以看到如下圖一樣的畫面。

方形視圖圍繞著橢圓移動,但你無法看出什么門道。WWDC的session 229,介紹了一個方法,可以可視化的看到動畫的效果。你需要添加一個橋接頭(如果是用swift寫的項目),添加以下代碼。
[cpp] view plaincopy
@import UIKit;
#if DEBUG
@interface UIDynamicAnimator (AAPLDebugInterfaceOnly)
/// Use this property for debug purposes when testing.
@property (nonatomic, getter=isDebugEnabled) BOOL debugEnabled;
@end
#endif
這會暴露一些私有API,讓UIDynamicAnimator把debug模式打開。這能讓你觀察到空間扭曲的情況。在ViewController類中,把animator的debugEnable屬性設置為true。
[cpp] view plaincopy
animator.debugEnabled = true // Private API. See the bridging header.
現在,當你打開應用時,你就能夠看到UIFieldBehavior提供的空間扭曲了。

你同樣能夠看到視圖碰撞時,圍繞在方形和圓形上的的輪廓線。你還可以添加另外一些屬性,它們并非API 的標注屬性,但是可以在lldb中使用。比如debugInterval和debugAnimationSpeed,當你需要debug你的動畫時,它們會非常有幫助。
我們可以看到field起了作用,可以清楚的看到碰撞的效果。如果我們想tweak更多屬性,可以給對象設置具體的數值,然后重啟應用看看它的變化。我們給頁面添加三個UISlider控制組件,分別控制力量、平滑度和速度,力量的組件數值范圍在0-25,其他兩個都是0-1。

當你在Interface Builder中創建好,拖拽三個動作事件到ViewController類,然后按下面設置,更新它們的屬性。
[cpp] view plaincopy
@IBAction func smoothnessValueChanged(sender: UISlider) {
noiseField.smoothness = CGFloat(sender.value)
}
@IBAction func speedValueChanged(sender: UISlider) {
noiseField.animationSpeed = CGFloat(sender.value)
}
@IBAction func strengthValueChanged(sender: UISlider) {
noiseField.strength = CGFloat(sender.value)
}
現在,運行應用。你可以通過控制條來設置屬性的具體值,以觀察動畫的實際效果。

希望這些能夠讓你快速理解UIKit Dynamics里UIFieldBehavior和non-rectangular collision bounds APIs是怎么工作和debug的。我推薦你在真實的設備(而不是模擬器)中查看效果,否則你看不出motion所帶來的效果變化。
本站文章版權歸原作者及原出處所有 。內容為作者個人觀點, 并不代表本站贊同其觀點和對其真實性負責,本站只提供參考并不構成任何投資及應用建議。本站是一個個人學習交流的平臺,網站上部分文章為轉載,并不用于任何商業目的,我們已經盡可能的對作者和來源進行了通告,但是能力有限或疏忽,造成漏登,請及時聯系我們,我們將根據著作權人的要求,立即更正或者刪除有關內容。本站擁有對此聲明的最終解釋權。