玩转iOS9的UIDynamics

移动开发
UIDynamics在iOS7 SDK中是一个受欢迎的新加特性, 它基本上是一个支持UIView的物理引擎,可以让我们自定义UI 控件的物理特性。这个API浅显易懂,你可以轻松地创建很棒的动画或者过渡效果。之前我在 这篇文章 中已经涵盖了其基本要点, 而这一次,我们将看看iOS 9里的UIDynamics有什么新玩意。

UIDynamics在iOS7 SDK中是一个受欢迎的新加特性, 它基本上是一个支持UIView的物理引擎,可以让我们自定义UI 控件的物理特性。这个API浅显易懂,你可以轻松地创建很棒的动画或者过渡效果。之前我在 这篇文章 中已经涵盖了其基本要点, 而这一次,我们将看看iOS 9里的UIDynamics有什么新玩意。

碰撞边界(Collision Bounds)

UIDynamics 的***个版本带有碰撞系统(在 UICollisionBehavior 中)只支持矩形。这可以理解,因为UIViews都是矩形架构,但是圆形的却不常见,更不用说优化一个自定义的贝塞尔曲线。在iOS 9中,UIDynamicItem协议里加了一个新属性:UIDynamicItemCollisionBoundsType,支持以下枚举类型:

  • Rectangle
  • Ellipse
  • Path

这个属性是只读的,如果我们想修改它的话,需要提供我们的子类:

  1. class Ellipse: UIView { 
  2.   override var collisionBoundsType: UIDynamicItemCollisionBoundsType { 
  3.     return .Ellipse 
  4.   } 

这是默认碰撞边界的UIView。

[[140693]]

而这是同一个带.Ellipse属性的UIView。

[[140694]]

这涵盖了圆形的视图,如果我们突发奇想,画一个更复杂,有连续的刚体,我们就可以使用.path枚举类型啦,并且也要重写该属性:  

  1. var collisionBoundingPath: UIBezierPath { get } 

这个路线可以是任意你能所想到的,只要它的样子是凸面的(即任意在多边形内两个的点,两点间的线段完全包含在多边形内),并且是逆时针绕的。凸面这个条件也许限制的太死了,于是引入了UIDynamicItemGroup,它可以详细描绘一组不同图形的的组合图形。这样,只要该组合中的每个图形都是凸面,即使得到的多边形是凹面的也OK。

Field Behavior

Field Behavior是在整个场景中运用的一种新behavior。一个最普通的例子就是我们一直默默地使用着的UIGravityBehavior,即场景中的每个物体都会受到一个向下的重力。现在我们可以使用一组新的场力,就像径向(距离场景中心越近,力越大)、噪声(在场景内随机产生的不同的力)等等。

Dynamic Item Behavior(动力元素行为)

UIDynamicItemBehavior 包含了几个有趣的新特性:

  • var charge: CGFloat
  • var anchored: Bool

charge 代表能够影响一个元素在电磁场上如何移动的电荷(是的,听起来很疯狂),而anchored本质上是将图形变成了碰撞中的一个静态物体,但没有响应事件(如果有什么东西撞上了它,它会丝毫不动),所以可以***地用来表示地板或墙壁。

Attachment Behavior(吸附行为)

UIAttachmentBehavior改进后,现在像个具有新方法和属性的侦探,如同frictionTorque和attachmentRange一样。现在吸附行为变得更加灵活,我们可以指定相对滑动的动作、固定吸附、绳索链接和我最喜欢的:针型吸附。想像一下两个钉在一起的物体你就明白了。这些基本涵盖了UIDynamics的新特性,现在,是时候丢下这个更新日志,并开始搭建一些很二的东西了。

让我们玩球吧

我上周花了很多时间在球王Ball King)上。这一个很棒的消磨时间的东西,这游戏的的理念很简单,但是表现很出色。并且,它采用了获得苹果设计奖的Crossy Roadde 相同的理念:它不会以任何方式影响到玩家,比如游戏内的荣誉。

[[140695]]

我非常喜欢它的一点是球的物理模型,以及当球打到篮板上时篮板的反应。看起来用它来测试上面提到的UIDynamics新特性应该会非常不错。让我们来看看如何一步步地打造属于自己的简单的版本吧:BallSwift

篮框

篮球架可以用一个UIView作为篮板,几个UIView作为篮框的左右两边,最前面的view作为篮框本身(不带物理刚体)。使用我们之前定义的类Ellipse,我们就可以创造我们的游戏场景的视觉表现:

  1. /* 
  2. Build the hoop, setup the world appearance 
  3. */ 
  4. func buildViews() { 
  5.   board = UIView(frame: CGRect(x: hoopPosition.x, y: hoopPosition.y, width: 100, height: 100)) 
  6.   board.backgroundColor = .whiteColor() 
  7.   board.layer.borderColor = UIColor(red: 0.98, green: 0.98, blue: 0.98, alpha: 1).CGColor 
  8.   board.layer.borderWidth = 2 
  9.    
  10.   board.addSubview({ 
  11.     let v = UIView(frame: CGRect(x: 30, y: 43, width: 40, height: 40)) 
  12.     v.backgroundColor = .clearColor() 
  13.     v.layer.borderColor = UIColor(red: 0.4, green: 0.4, blue: 0.4, alpha: 1).CGColor 
  14.     v.layer.borderWidth = 5 
  15.     return v 
  16.     }()) 
  17.    
  18.   leftHoop = Ellipse(frame: CGRect(x: hoopPosition.x + 20, y: hoopPosition.y + 80, width: 10, height: 6)) 
  19.   leftHoop.backgroundColor = .clearColor() 
  20.   leftHoop.layer.cornerRadius = 3 
  21.    
  22.   rightHoop = Ellipse(frame: CGRect(x: hoopPosition.x + 70, y: hoopPosition.y + 80, width: 10, height: 6)) 
  23.   rightHoop.backgroundColor = .clearColor() 
  24.   rightHoop.layer.cornerRadius = 3 
  25.    
  26.   hoop = UIView(frame: CGRect(x: hoopPosition.x + 20, y: hoopPosition.y + 80, width: 60, height: 6)) 
  27.   hoop.backgroundColor = UIColor(red: 177.0/255.0, green: 25.0/255.0, blue: 25.0/255.0, alpha: 1
  28.   hoop.layer.cornerRadius = 3 
  29.    
  30.   [board, leftHoop, rightHoop, floor, ball, hoop].map({self.view.addSubview($0)}) 

这里其实没有什么新东西, 篮框以编程形式被创建在常量CGPoint hoopPosition上。但是视图的顺序很重要,因为我们想让篮框要高于篮球(的抛投点)。

#p#

螺母和螺栓(Nuts and bolts)

篮框的最重要的部分是左右臂,它们需要一个物理圆形身体(使得与球的碰撞显得自然),需要用螺栓固定在板和前框。这两个将成为基本的UIDynamicItems,并不会直接碰撞参与碰撞。新推出的针型吸附就是为此而生的,它可以把一切都***地结合在一起,因为我们可以在这个比较粗糙的图画上看到:

[[140696]]

在给定的确定空间点内,pin一次仅可连接几个视图:

  1. let bolts = [ 
  2.   CGPoint(x: hoopPosition.x + 25, y: hoopPosition.y + 85), // leftHoop -> Board 
  3.   CGPoint(x: hoopPosition.x + 75, y: hoopPosition.y + 85), // rightHoop -> Board 
  4.   CGPoint(x: hoopPosition.x + 25, y: hoopPosition.y + 85), // hoop -> Board (L) 
  5.   CGPoint(x: hoopPosition.x + 75, y: hoopPosition.y + 85)] // hoop -> Board (R) 
  6.    
  7. // Build the board 
  8. zip([leftHoop, rightHoop, hoop, hoop], offsets).map({ 
  9.   (item, offset) in 
  10.   animator?.addBehavior(UIAttachmentBehavior.pinAttachmentWithItem(item, attachedToItem: board, attachmentAnchor: bolts)) 
  11. }) 

如果你不准备继续看看swfit版里的奇妙的功能,那你很可能不熟悉zip和map。这一开始看起来似乎有些刻意而为,但其实很简单:每个视图都用一个偏移点钉住吸附,然后我们得到一系列的元组,稍后将会在映射函数中使用。顾名思义,它创建了所给定的物体的数组中的每个元素间的映射。这使得篮框的左右边都用螺栓固定在板和前框,如下:

  • 左臂用螺栓固定在篮板的左侧
  • 右臂用螺栓固定在篮板右侧
  • 篮框用螺栓固定在篮板的左侧

下一个步骤要求我们将篮板悬挂上,别将它固定死,这样球一个碰撞就可以使得它转动,就像在Ball King这个游戏中一样:

  1. // Set the density of the hoop, and fix its angle 
  2. // Hang the hoop 
  3. animator?.addBehavior({ 
  4.   let attachment = UIAttachmentBehavior(item: board, attachedToAnchor: CGPoint(x: hoopPosition.x, y: hoopPosition.y)) 
  5.   attachment.length = 2 
  6.   attachment.damping = 5 
  7.   return attachment 
  8.   }()) 
  9.    
  10. animator?.addBehavior({ 
  11.   let behavior = UIDynamicItemBehavior(items: [leftHoop, rightHoop]) 
  12.   behavior.density = 10 
  13.   behavior.allowsRotation = false 
  14.   return behavior 
  15.   }()) 
  16.    
  17. // Block the board rotation 
  18. animator?.addBehavior({ 
  19.   let behavior = UIDynamicItemBehavior(items: [board]) 
  20.   behavior.allowsRotation = false 
  21.   return behavior 
  22.   }()) 

篮框已经做好了,下面该到篮球了,用一个圆形的自定义UIImageView子类视图,如同Ellipse类:

然后,我们可以将球作为一个普通的UIImageView实例化:

  1. let ball: Ball = { 
  2.   let ball = Ball(frame: CGRect(x: 0, y: 0, width: 28, height: 28)) 
  3.   ball.image = UIImage(named: "ball"
  4.   return ball 
  5. }() 

***我们设置他的 物理属性:

  1. // Set the elasticity and density of the ball 
  2. animator?.addBehavior({ 
  3.   let behavior = UIDynamicItemBehavior(items: [ball]) 
  4.   behavior.elasticity = 1 
  5.   behavior.density = 3 
  6.   behavior.action = { 
  7.     if !CGRectIntersectsRect(self.ball.frame, self.view.frame) { 
  8.       self.setupBehaviors() 
  9.       self.ball.center = CGPoint(x: 40, y: self.view.frame.size.height - 100
  10.     } 
  11.   } 
  12.   return behavior 
  13.   }()) 

这段代码里我设置了弹性大小(碰撞后反弹的幅度)、密度(就把它看作重量吧),还有当球超出弹跳范围时立即结束游戏的事件,即为重置游戏状态(在主视图中)。

Collisions and gravity(碰撞和重力)

我提到了UIDynamicItemBehavior的新属性anchored,即禁用了对象的动态behavior,同时将其保留在在碰撞循环里。听起来用它来搭建一个坚固的地板会很不错:

  1. // Anchor the floor 
  2. animator?.addBehavior({ 
  3.   let behavior = UIDynamicItemBehavior(items: [floor]) 
  4.   behavior.anchored = true 
  5.   return behavior 
  6.   }()) 

如果你忘了设置这个属性,你就会抓耳挠腮。反正我是这样的。

好吧,一切都设置好啦,现在只需要一些重力和一组碰撞事件了:

  1. animator?.addBehavior(UICollisionBehavior(items: [leftHoop, rightHoop, floor, ball])) 
  2. animator?.addBehavior(UIGravityBehavior(items: [ball])) 

重力是应用在默认每秒一点作为的向下的力的场景behavior。碰撞behavior 作为相互碰撞的view的参数。游戏已经搭建好啦,现在我们可以在球上施加一个瞬发力,并用我们的手指划过屏幕:

  1. let push = UIPushBehavior(items: [ball], mode: .Instantaneous) 
  2. push.angle = -1.35 
  3. push.magnitude = 1.56 
  4. animator?.addBehavior(push) 

ball.gif

这下你该明白了吧,虽然场景边缘真的画的很low,但是搭建它真的很有趣(是的,云朵和灌木丛都是一样的勾画,就像 超级马里奥 里中的)。

老规矩,你可以在我们的 GitHub页面 找到源代码。

下次见

责任编辑:倪明 来源: fancypixel的博客
相关推荐

2015-10-16 14:27:29

iOS9collectionV特性

2015-09-16 09:55:12

ios9学习UIKit Dynam

2015-08-24 09:24:21

ios学习contacts fr

2015-08-20 09:00:23

ios9api

2015-07-02 17:32:28

iOS 9苹果

2015-09-25 09:44:24

ios9MapkitTrans

2016-03-18 11:19:57

ios9replaykit入门

2015-09-16 09:02:10

IOS9新功能搜索业务

2015-09-09 15:51:54

2015-11-11 10:17:15

ios9联系人框架干货

2015-05-19 09:51:57

WWDCiOS9

2015-10-20 09:36:52

苹果iOS 9字体

2015-10-29 11:13:23

iOS9使用框

2015-09-10 09:31:43

IOS9IOS9.1

2015-08-24 09:19:05

ios9split scree

2015-08-21 09:47:02

ios9sdk新特性

2015-08-20 09:19:46

ios9uistackview

2015-07-27 09:04:33

ios9

2015-05-28 23:29:01

xy

2015-08-03 10:03:09

iosuistack vie
点赞
收藏

51CTO技术栈公众号