翻译自raywenderlich网站iOS教程Graphics & Animation系列
介绍UIKit Dynamics
是一个集成到UIKit中的完整物理引擎。它容许您经过添加诸如重力,附件(弹簧)和力量等行为来建立感受真实的界面。您定义了您但愿界面元素采用的物理特征,动态引擎负责其他部分。git
Motion Effects
能够建立很酷视差效果,就像在倾斜iOS 7主屏幕时看到的同样。基本上,咱们能够利用手机加速计提供的数据来建立对手机方向变化做出反应的接口。github
当一块儿使用时,运动和动态成为用户体验工具的重要组成部分,使您的交互栩栩如生。用户将经过看到它以天然,动态的方式回应他们的行为。编程
准备开始swift
在ViewController.swift
添加以下代码在viewDidLoad
:框架
let square = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 100)) square.backgroundColor = UIColor.grayColor view.addSubview(square)
运行以后效果以下:ide
增长重力效果
仍然在 ViewController.swift
中,在viewDidLoad
上方添加如下属性:工具
var animtor: UIDynamicAnimator! var gravity: UIGravityBehavior!
这些属性是隐式解包的optionals(如类型名称后面的!所示)。 这些属性必须是可选的,由于咱们没有在init方法中初始化它们。 此时可使用隐式解包的optionals,由于咱们知道这些属性在初始化后不会为零。 能够防止每次使用的时候须要!来解包。动画
添加如下代码在viewDidLoad
的结尾处:网站
animtor = UIDynamicAnimator(referenceView: view) gravity = UIGravityBehavior(items: [square]) animtor.addBehavior(gravity)
如今构建并运行应用程序。 能够看到你的方块慢慢地开始加速,直到它落在屏幕的底部。
在刚刚添加的代码中,这里有几个动态类:ui
UIDynamicAnimator
是UIKit物理引擎。这个类跟踪你添加到引擎的各类行为,好比引力,并提供总体上下文。当建立animator的实例时,将传入animator用于定义其坐标系的参考视图。UIGravityBehavior
模拟重力的行为并对一个或多个项目施加做用力,能够建模物理交互。当建立一个行为的实例时,将它与一组项目相关联 - 一般是视图。 经过这种方式,能够选择哪些项目受到行为的影响,在这种状况下哪些项目会受到重力的影响。大多数行为都有一些配置属性;例如,重力行为能够改变它的角度和大小。尝试修改这些属性以使对象以不一样的加速度向上,侧向或对角线倾斜。
注:关于单位的简单说法:在物理世界中,重力(g)以米每平方秒表示,大约等于9.8米/秒2。 使用牛顿第二定律,你能够用下面的公式计算物体在重力影响下的落差: distance = 0.5 × g × time2 在UIKit Dynamics中,公式相同,但单位不一样。使用每秒数千像素单位的单位 ,而不是米。 使用牛顿第二定律,仍然能够根据提供的重力组件随时计算出视角。
固然咱们并不须要知道这些细节,只须要知道g值越大意味着物体降低的越快。
设置边界
为了保持方块在屏幕的边界内,须要定义一个边界。
添加另外一个属性在 ViewController.swift
var collision: UICollisionBehavior!
在viewDidLoad:
下面添加这几行:
collision = UICollisionBehavior(items: [square]) collision.translatesReferenceBoundsIntoBoundary = true animtor.addBehavior(collision)
上面的代码建立了一个碰撞行为,它定义了一个或多个关联项与之交互的边界。
上述代码不是明确添加边界坐标,而是将translatesReferenceBoundsIntoBoundary
属性设置为true
。 这会致使边界使用提供给UIDynamicAnimator的参考视图的边界。
运行时能够看到正方形与屏幕底部碰撞,稍微反弹,而后中止,以下所示:
以上咱们用不多的代码实现了一个很酷的效果
处理碰撞
接下来,添加一个不可移动的障碍,降低的方块将碰撞和互动。
将如下代码插入viewDidLoad
中添加square
的代码下面:
let barrier = UIView(frame: CGRect(x: 0, y: 300, width: 130, height: 20)) barrier.backgroundColor = UIColor.red view.addSubview(barrier)
构建并运行你的应用程序; 你会在屏幕上看到一个红色的“障碍”。 然而,事实证实,这个障碍并非那么有效:
这不是咱们想要的效果,但它确实提供了一个重要的提示:动态只会影响与行为相关的视图:
UIDynamicAnimator
与提供坐标系的参考视图相关联。 而后添加一个或多个行为,这些行为会对与其相关联的项目施加做用力。 大多数行为能够与多个项目相关联,而且每一个项目能够与多个行为相关联。 上图显示了应用中的当前行为及其关联。
当前代码中的任何行为都不能“意识到”屏障,因此就下层动态引擎而言,屏障甚至不存在。
让对象响应碰撞
为了使正方形与障碍碰撞,找到初始化碰撞行为的代码并将其替换为如下内容:
collision = UICollisionBehavior(items: [square, barrier])
碰撞对象须要知道它应该与之交互的每一个视图; 所以将障碍添加到物品列表中容许碰撞物体也做用于障碍物。
构建并运行应用程序; 这两个对象相互碰撞并相互做用,以下图所示:
碰撞行为在与其相关的每一个项目周围造成“边界”; 这将它们从能够经过彼此的对象变成更坚实的对象。
更新前面的图,能够看到碰撞行为如今与两个视图相关联:
可是,这两个对象之间的交互仍然存在不太正确的地方。 屏障被认为是不可移动的,可是当两个物体在当前配置中碰撞时,屏障会被打破位置并开始向屏幕底部旋转。
更奇怪的是,屏障从屏幕底部反弹而且不像平方那样安定下来 - 这颇有意义,由于重力行为不会与屏障相互做用。 这也解释了为何屏障不会移动,直到正方形与它碰撞。
如今须要一个不一样的方法来解决问题。 因为障碍视图是不可移动的,因此动力学引擎不须要知道它的存在。 可是如何检测到碰撞?
看不见的边界和碰撞
将碰撞行为初始化更改回其原始形式,以便仅识别方块:
collision = UICollisionBehavior(items: [square])
紧随此行后,添加如下内容:
collision.addBoundary(withIdentifier: "barrier" as NSCopying, for: UIBezierPath(rect: barrier.frame))
上面的代码添加了一个与屏障视图具备相同框架的不可见边界。 红色屏障对用户而言仍然可见,但对动态引擎不可见,而边界对动态引擎可见但对用户不可见。 当方块落下时,它彷佛与屏障相互做用,但它实际上碰撞了不动的边界。
构建并运行,以下所示:
方块如今从边界反弹,旋转一点,而后继续往屏幕底部前进的地方休息。
到目前为止,UIKit Dynamics
的功能已经变得至关清晰:只需几行代码就能够完成不少工做。 引擎盖下有不少事情要作, 下一节将向展现动态引擎如何与应用程序中的对象交互的一些细节。
碰撞的细节
每一个动态行为都有一个动做属性。 将如下代码添加到viewDidLoad
中:
collision.action = { print("\(NSStringFromCGAffineTransform(self.square.transform)) \(NSStringFromCGPoint(self.square.center))") }
上面的代码记录降低方块的中心和变换属性。运行应用程序,将在Xcode控制台窗口中看到这些日志消息。
对于1〜400毫秒,日志消息:
[1, 0, 0, 1, 0, 0], {150, 236} [1, 0, 0, 1, 0, 0], {150, 243} [1, 0, 0, 1, 0, 0], {150, 250}
在这里能够看到动态引擎正在改变每一个动画步骤中的方块的中心 - 也就是它的帧。
只要方块碰到屏障,它就开始旋转,这会产生以下的日志消息:
[0.99797821, 0.063557133, -0.063557133, 0.99797821, 0, 0] {152, 247} [0.99192101, 0.12685727, -0.12685727, 0.99192101, 0, 0] {154, 244} [0.97873402, 0.20513339, -0.20513339, 0.97873402, 0, 0] {157, 241}
在这里能够看到,动态引擎正在使用变换和帧偏移的组合来根据底层物理模型定位视图。
虽然动态适用于这些属性的确切值可能没有多大意义,但知道它们正在被应用很重要。 所以,若是以编程方式更改对象的框架或转换属性,则能够预期这些值将被覆盖。 这意味着当它处于动态的控制之下时,不能使用变换来缩放对象。
将dynamic behaviors应用于对象的惟一要求是它采用UIDynamicItem协议,以下所示:
protocol UIDynamicItem : NSObjectProtocol { var center: CGPoint { get set } var bounds: CGRect { get } var transform: CGAffineTransform { get set } }
UIDynamicItem
协议提供动态读写访问中心和变换属性,容许它根据其内部计算移动项目。 它还具备对边界的读取权限,用于肯定项目的大小。 这容许它在物品的周边周围产生碰撞边界,而且在施加力时计算物品的质量。
这个协议意味着动态与UIView
不紧密耦合; 的确有另外一个UIKit
类不是视图,但仍然采用这个协议:UICollectionViewLayoutAttributes。
碰撞通知
到目前为止,已经添加了一些视图和行为,而后让动态接管。 在下一步中,将了解如何在物品碰撞时接收通知。
仍然在ViewController.swift
中,经过更新类声明来采用UICollisionBehaviorDelegate
协议:
class ViewController: UIViewController, UICollisionBehaviorDelegate {
在viewDidLoad
中,在初始化碰撞对象以后将视图控制器设置为委托,以下所示:
collision.collisionDelegate = self func collisionBehavior(_ behavior: UICollisionBehavior, beganContactFor item: UIDynamicItem, withBoundaryIdentifier identifier: NSCopying?, at p: CGPoint) { print("Boundary contact occurred - \(String(describing: identifier))") }
这种委托方法在发生冲突时被调用。 它将打印出一条日志消息给控制台。 为了不使用大量消息弄乱控制台日志,请删除在上一节中添加的collision.action日志记录。
运行将在控制台中看到如下条目:
Boundary contact occurred - Optional(barrier) Boundary contact occurred - Optional(barrier)
从上面的日志消息中能够看到方块与“标识符”(barrierView
)屏障相撞两次;
在print下面添加如下内容:
let collidingView = item as! UIView UIView.animate(withDuration: 1) { collidingView.backgroundColor = .gray }
上面的代码将碰撞项目的背景颜色淡化为灰色。
构建并运行以查看这种效果:
到目前为止,UIKit Dynamics已经根据物品的界限自动设置物品的物理属性(如质量和弹性)。 接下来,将看到如何使用UIDynamicItemBehavior
类本身控制这些物理属性。
配置item属性
在viewDidLoad
中,将如下内容添加到方法的末尾:
let itemBehaviour = UIDynamicItemBehavior(items: [square]) itemBehaviour.elasticity = 0.6 animtor.addBehavior(itemBehaviour)
上面的代码建立一个项目行为,将其与方块关联,而后将行为对象添加到动画设计器中。 弹性属性控制着物品的弹性; 值为1.0表示彻底弹性碰撞; 也就是说,在碰撞中没有能量或速度丢失的地方。 咱们将方块的弹性设置为0.6,这意味着每次反弹时平方将失去速度。
构建并运行你的应用程序,你会注意到这个广场如今表现得更加酷,以下所示:
注:制做上面的图片并显示方块路径轮廓的代码以下: func addPosition() { var updateCount = 0 collision.action = { if updateCount % 3 == 0 { let outLine = UIView(frame: self.square.bounds) outLine.transform = self.square.transform outLine.center = self.square.center outLine.alpha = 0.5 outLine.backgroundColor = .clear outLine.layer.borderColor = self.square.layer.presentation()?.backgroundColor outLine.layer.borderWidth = 1.0 self.view.addSubview(outLine) } updateCount += 1 } }
在上面的代码中,只改变了物品的弹性; 然而,该项目的行为类有许多其余属性能够在代码中操做。 以下:
elasticity - 决定“弹性”碰撞的方式,即物体在碰撞中的弹性或“橡胶状”程度。 friction - 决定沿表面滑动时的运动阻力。 density - 当与大小相结合时,这将给出物品的总体质量。质量越大,加速或减速物体越难。 resistance - 决定抵抗任何线性移动的数量。这与仅适用于滑动运动的摩擦造成对比。 angularResistance - 肯定抵抗任何旋转运动的量。 allowsRotation - 若是将此属性设置为NO,则无论发生的旋转力如何,对象都不会旋转。
动态添加行为
在下一步中,将看到如何动态地添加和删除行为。
打开ViewController.swift
并在viewDidLoad
上方添加如下属性:
var firstContact = false
将如下代码添加到碰撞代理方法的末尾func collisionBehavior(_ behavior: UICollisionBehavior, beganContactFor item: UIDynamicItem, withBoundaryIdentifier identifier: NSCopying?, at p: CGPoint)
if !firstContact { firstContact = true let square = UIView(frame: CGRect(x: 30, y: 0, width: 100, height: 100)) square.backgroundColor = .gray view.addSubview(square) collision.addItem(square) gravity.addItem(square) let attach = UIAttachmentBehavior(item: collidingView, attachedTo: square) animtor.addBehavior(attach) }
上面的代码检测屏障和正方形之间的初始接触,建立第二个正方形并将其添加到碰撞和重力行为中。 另外,还能够设置一个附件行为,以建立用虚拟弹簧附加一对对象的效果。
构建并运行; 当原始方块碰到屏障时,应该会看到一个新的方块,以下所示:
用户交互
正如刚刚看到的,当物理系统已经运动时,咱们能够动态添加和删除行为。 在最后一节中,每当用户点击屏幕时,都会添加另外一种类型的动态行为UISnapBehavior
。 一个UISnapBehavior
使一个对象跳跃到一个有弹性的弹簧式动画的指定位置。
删除上一节添加的代码:collisionBehavior()
中的firstContact
属性和if语句。 在屏幕上只能看到一个方块的UISnapBehavior效果会更容易。
在viewDidLoad
上添加两个属性:
var square: UIView! var snap: UISnapBehavior!
这将跟踪方块视图,以便您能够从视图控制器的其余位置访问它。 您将在下一个使用捕捉对象。
在viewDidLoad
中,从square声明中删除let关键字,以便它使用新属性而不是局部变量:
square = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
最后,为touchesEnded
添加一个实现,以在用户触摸屏幕时建立并添加新的捕捉行为:
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { if snp != nil { animtor.removeBehavior(snp) } let touchs = touches as NSSet let touch = touchs.anyObject() as! UITouch snp = UISnapBehavior(item: square, snapTo: touch.location(in: view)) animtor.addBehavior(snp) }
这段代码很是简单。 首先,它检查是否存在现有的快照行为并将其删除。 而后建立一个新的捕捉行为,将方块对齐到用户的触摸位置,并将其添加到动画制做工具中。
构建并运行应用程序。 尝试点击; 方块会跑到触摸的地方
这里是最终demo,此demo是raywenderlich下面iOS的Graphics & Animation整个教程系列的集合。