UIKit Dynamics 置身真实世界

图片来源于网络

前言:

iOS的设计目标鼓励您创建数字接口(digital interface),对触摸,手势和方向的变化做出反应,就好像它们是物理对象而不仅仅是简单的像素集合。可以使用户可以通过皮肤深层的自身形态与界面更深层次的联系。

工具介绍:
  • UIKit Dynamics是整合到UIKit中的完整物理引擎。它允许您通过添加重力,附件(弹簧)和力等行为来创建感觉真实的界面。您定义了您希望您的界面元素采用的物理特征,动力学引擎将照顾其余部分。
  • Motion Effects使您可以创建炫酷视差效果。基本上,您可以利用手机加速度计提供的数据,以创建响应手机方向变化的界面。
一、着手

打开ViewController.swift,并将以下代码添加到下面的代码viewDidLoad:

let square = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
square.backgroundColor = UIColor.gray
view.addSubview(square)
二、加重力
animator = UIDynamicAnimator(referenceView: view)
gravity = UIGravityBehavior(items: [square])
animator.addBehavior(gravity)
  • UIDynamicAnimator是UIKit物理引擎。该类跟踪您添加到引擎的各种行为,例如重力,并提供整体上下文。创建动画制作实例时,您将传递animator用于定义其坐标系的参考视图。
  • UIGravityBehavior模拟重力的行为并在一个或多个项目上施加力,从而允许您建模物理交互。创建行为实例时,将其与一组项目(通常是视图)相关联。这样,您可以选择哪些项目受到行为的影响,在这种情况下,引力影响哪些项目。

大多数行为具有许多配置属性; 例如,重力行为允许您改变其角度和幅度。尝试修改这些属性,使您的对象以不同的加速度下降,侧面或对角线。
注意:单位上的一个简单单词:在物理世界中,重力(g)以米/秒表示,大约等于9.8 m/s2。使用牛顿第二定律,您可以用下列公式计算物体在重力影响下的距离:
distance = 0.5 × g × time2
在UIKit Dynamics中,公式是相同的,但单位是不同的。而不是米,您可以使用每秒成千上万个像素的单位。使用牛顿第二定律,您仍然可以根据您提供的重力组件随时确定您的view在何处。

三、设置边界

即使在屏幕底部消失后,它也会继续下降。为了将其保留在屏幕的边界内,您需要定义边界

var collision: UICollisionBehavior!
collision = UICollisionBehavior(items: [square])
collision.translatesReferenceBoundsIntoBoundary = true
animator.addBehavior(collision)

上述代码创建了一个碰撞行为,该行为定义了一个或多个相关联项目与之相关联的边界。
而不是明确添加边界坐标,上述代码将translatesReferenceBoundsIntoBoundary 属性设置为true。这导致边界提供给UIDynamicAnimator参考视图的边界。

四、处理碰撞

添加一个不可移动的障碍,下降的正方形将与之相冲突。

let barrier = UIView(frame: CGRect(x: 0, y: 300, width: 130, height: 20))
barrier.backgroundColor = UIColor.red
view.addSubview(barrier)

确实提供了一个重要的提醒:dynamics只影响与行为相关联的视图
大多数行为可以与多个项目相关联,并且每个项目可以与多个行为相关联

五、使对象响应碰撞

为了使square与障碍物相撞,请找到初始化碰撞行为的行,并将其替换为以下内容:

collision = UICollisionBehavior(items: [square, barrier])

碰撞对象需要知道它应该与之相互作用的每个视图; 因此,将项目列表中的障碍添加到允许碰撞对象也可以作用在障碍物上。
效果如下:

与障碍物碰撞

可以看出,square跟障碍物交互不是很正确,障碍物应该不可移动,更奇怪的是障碍物从屏幕的底部反弹,并不像square那样沉稳,因为重力行为与障碍物无关

六、隐形边界和碰撞

将碰撞行为初始化更改回最初

collision = UICollisionBehavior(items: [square])

在这一行之后,添加一下内容:(添加跟barrier相同frame的boundary)

collision.addBoundary(withIdentifier: "barrier" as NSCopying, for: UIBezierPath(rect: barrier.frame))   

红色障碍物对用户仍旧可见,而对动力引擎(dynamics engine)不可见;相反边界(boundary)对动力引擎可见,对用户不可见
随着square的下降,它似乎与barrier相互作用,但它实际上是与不可动的boundary相撞。

与障碍物碰撞2

下面将展示动态引擎如何与应用程序中的对象进行交互的一些细节。

七、在碰撞的背后

每个动态行为(dynamic behavior)都有个一个action属性,你可以在action属性中提供要在动画每一步执行的block,讲下列代码添加到viewDidLoad:

collision.action = {
  print("\(NSStringFromCGAffineTransform(square.transform)) \(NSStringFromCGPoint(square.center))")
}

打印日志如下:(动力引擎对square的frame的影响)

[1, 0, 0, 1, 0, 0] {150, 212}
[1, 0, 0, 1, 0, 0] {150, 218}
[1, 0, 0, 1, 0, 0] {150, 224}
[1, 0, 0, 1, 0, 0] {150, 231}
[1, 0, 0, 1, 0, 0] {150, 237}
[1, 0, 0, 1, 0, 0] {150, 244}

一旦方块击中障碍物,它就开始旋转,这样会产生如下的日志信息:

[0.9999181, 0.01279965, -0.01279965, 0.9999181, 0, 0] {150, 249}
[0.99820054, 0.059964005, -0.059964005, 0.99820054, 0, 0] {152, 249}
[0.9942596, 0.10699479, -0.10699479, 0.9942596, 0, 0] {154, 249}
[0.98810399, 0.15378727, -0.15378727, 0.98810399, 0, 0] {155, 249}
[0.97978747, 0.20004122, -0.20004122, 0.97978747, 0, 0] {157, 250}
[0.96930701, 0.24585338, -0.24585338, 0.96930701, 0, 0] {158, 251}

从打印日志,可以看到动态引擎正在使用变换(transform)和frame偏移(frame offset)的来改变view的position
如果在动画过程中,我们通过代码改变方块的frame和transform属性,物体属性会被我们重写,也就是说,动力学控制过程中,我们不应该通过transform来缩放物体等。
动力行为赋予的对象是term items而不是view。因此,拥有动力行为的对象需要遵守UIDynamicItem协议:

protocol UIDynamicItem : NSObjectProtocol {
  var center: CGPoint { get set }
  var bounds: CGRect { get }
  var transform: CGAffineTransform { get set }
}

UIDynamicItem协议给dynamics读写访问中心和转换属性(the center and transform properties),允许它基于其内部计算移动items。它还具有对边界的读取访问权限,它用于确定items的size,这样可以在items周边创建碰撞边界,并在施加力时计算物品的质量。
这个协议意味着动态不紧密耦合UIView; 确实有另一个UIKit类不是视图,但仍然采用这个协议:UICollectionViewLayoutAttributes。这允许dynamics动画在集合视图中对items进行动画。

八、碰撞通知

添加UICollisionBehaviorDelegate

class ViewController: UIViewController, UICollisionBehaviorDelegate {

添加一个协议方法

func collisionBehavior(_ behavior: UICollisionBehavior, beganContactFor item: UIDynamicItem, withBoundaryIdentifier identifier: NSCopying?, at p: CGPoint) {
    print("Boundary contact occurred - \(identifier)")
}

打印日志如下:

Boundary contact occurred - Optional(barrier)
Boundary contact occurred - Optional(barrier)
Boundary contact occurred - Optional(barrier)
Boundary contact occurred - Optional(barrier)
Boundary contact occurred - nil
Boundary contact occurred - nil
Boundary contact occurred - nil
Boundary contact occurred - nil

square与带标识barrier的边界相撞四次

为了方便看,我们改一下square的背景颜色,每次撞击边界时,方形将闪烁黄色。在collisionBehavior方法里面加以下代码

let collidingView = item as! UIView
collidingView.backgroundColor = UIColor.yellow
UIView.animate(withDuration: 0.3) {
    collidingView.backgroundColor = UIColor.gray
}

到目前为止,UIKit Dynamics通过根据您的项目的界限进行计算,自动设置物品的物理属性(如质量和弹性)。接下来,您将看到如何通过使用UIDynamicItemBehavior该类自己来控制这些物理属性。

九、配置item属性

上述代码创建一个item行为,将其与square相关联,然后将该行为对象添加到动画制作器。弹性属性控制物品的柔软度; 值为1.0表示完全弹性的碰撞; 也就是说,碰撞中没有能量或速度损失。您将您的square的弹性设置为0.6,这意味着每次弹跳时,平方将失去速度。
我们加以下代码,有线框,方便看

var updateCount = 0
collision.action = {
    if (updateCount % 3 == 0) {
        let outline = UIView(frame: square.bounds)
        outline.transform = square.transform
        outline.center = square.center
        
        outline.alpha = 0.5
        outline.backgroundColor = UIColor.clear
        outline.layer.borderColor = square.layer.presentation()?.backgroundColor
        outline.layer.borderWidth = 1.0
        self.view.addSubview(outline)
    }
    
    updateCount += 1
}

在上面的代码中,只改变了项目的弹性; 但是,该项目的行为类具有可以在代码中操作的其他许多属性。它们如下:

  • 弹性(elasticity) - 决定弹性的碰撞将如何,即项目在碰撞中的弹性或“橡皮”。
  • 摩擦(friction) - 确定沿着表面滑动时的阻力运动量。
  • 密度(density) - 当与尺寸结合时,这将给出物品的总体质量。质量越大,加速或减速物体越难。
  • 电阻(resistance) - 确定任何线性运动的阻力量。这与仅适用于滑动 运动的摩擦相反。
  • angularResistance - 确定任何旋转运动的阻力量。
  • allowRotation - 这是一个有趣的,不建模任何现实世界的物理属性。将此属性设置为“否”,无论发生何种旋转力,对象都不会旋转。
十、动态添加行为

下面,介绍如何动态添加和删除行为。
首先添加一下属性

var firstContact = false

将以下代码添加到碰撞委托方法(collisionBehavior)的末尾

if (!firstContact) {
    firstContact = true
    
    let square = UIView(frame: CGRect(x: 30, y: 0, width: 100, height: 100))
    square.backgroundColor = UIColor.gray
    view.addSubview(square)
    
    collision.addItem(square)
    gravity.addItem(square)
    
    let attach = UIAttachmentBehavior(item: collidingView, attachedTo:square)
    animator.addBehavior(attach)
}

上述代码检测到barriersquare之间的初始接触,创建第二个square并将其添加到碰撞和重力行为。
效果如下:

此外,您还可以设置 attachment 行为,以创建使用虚拟弹簧连接一对对象的效果。

用户交互

添加另一种类型的动态行为——UISnapBehavior,当用户点击时,UISnapBehavior 让对象以弹簧般动画效果跳到一个特定的位置

现在移除firstContact属性以及在collisionBehavior()添加它的的代码

touchesEnded方法中加入下面代码

override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
  if (snap != nil) {
    animator.removeBehavior(snap)
  }
 
  let touch = touches.anyObject() as UITouch 
  snap = UISnapBehavior(item: square, snapToPoint: touch.locationInView(view))
  animator.addBehavior(snap)
}

这段代码很简单。首先,它检查是否存在现有的捕捉行为(snap behavior)并将其删除。然后创建一个新的捕捉行为,将square对齐到用户触摸的位置,并将其添加到动画制作工具(animator)。
现在你可以随便点击屏幕,square会跳到你点击的位置。
效果如下:

下一篇UIKit Dynamics 的介绍
Dynamics 投掷效果

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容