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相撞。

下面将展示动态引擎如何与应用程序中的对象进行交互的一些细节。 #####七、在碰撞的背后 每个动态行为(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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值