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 投掷效果

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏GIS讲堂

openlayers4结合高德地图API实现路径规划

21920
来自专栏老司机的简书

老司机带你走进Core Animation 之粒子发射、TileLayer与异步绘制

老司机带你走进Core Animation 之粒子发射、TileLayer与异步绘制

12520
来自专栏python小白到大牛

用 Python 实现打飞机,让子弹飞吧!

安装好 pygame 在第一次使用 pygame 的时候,pyCharm 会自动 install pygame。

27830
来自专栏非著名程序员

Android:一个高效的UI才是一个拉风的UI

开篇 Android是一个运行在移动终端上的操作系统,跟传统PC最大的不同所在就是移动终端的资源紧缺问题“比较”明显,当然对于一些屌丝机型,应该用“非常“来形容...

28390
来自专栏Jerry的SAP技术分享

SAP成都研究院非典型程序猿,菜园子小哥:当我用UI5诊断工具时我用些什么

身边有些年轻同事曾经向我表达过这种困扰:尽管完成日常工作没有任何问题,但是还想更进一步,把代码写得更好些,做到精益求精。现在写的代码能实现功能,但是不知道可以怎...

17630
来自专栏BestSDK

表格设计的六种打开方式,正确提升表格的阅读效率

在设计数据类产品、后台配置产品时,PD 常常会指着一块地方说「这儿放个表格,需要有balabala…」,而表格的结构不外乎这几种类型: 垂直布局 水平布局 矩阵...

32450
来自专栏腾讯Bugly的专栏

优化安卓应用内存的神秘方法以及背后的原理,一般人我不告诉他

安卓应用一般都害怕自己被杀,内存占用高是被杀的重要原因之一,所以大家都想尽各种招数应对,但效果都一般。 但有一招: WindowManagerGlobal.ge...

93960
来自专栏Coding迪斯尼

使用物理引擎Box2D设计类愤怒小鸟的击球游戏--基本架构设置

21450
来自专栏lhyt前端之路

css菜鸡的自我救赎0. 前言1. 一些实践方案深入浅出2. 开始试试水

我们看百度搜索的顶部,顶部的#head(搜索框这一行都是)是fixed的,紧接着的那个div是一个tab。当然fixed脱离文本流,就用padding把自己的主...

10630
来自专栏大数据文摘

手把手 | 教你爬下100部电影数据:R语言网页爬取入门指南

29970

扫码关注云+社区

领取腾讯云代金券