天天品尝iOS7甜点 :: Day 0 :: UIKit Dynamics

这篇文章是天天品尝iOS7甜点系列的一部分,你可以查看完整的系列目录:天天品尝iOS7甜点


伴随着介绍苹果的iOS7,将会使你清晰的认清设备和现实世界的相互作用,UIKit Dynamics就是众多新API中的一个, UIKit Dyanmics是UIKit下的一个二维的物理引擎,在今天的文章里面,我们就介绍一下UIKit Dynamics并且构建一个牛顿模拟重力实验。

本章的实例程序能够在github上面进行访问,访问地址:github.com/ShinobiControls/iOS7-day-by-day

物理世界

为了模拟现实世界中的物理效果,我们使用UIDynamicBehavior的子类来确定对象(这些对象都实现了UIDynamicItem协议)的不同行为。一些行为的例子包含:重力作用、碰撞作用和弹性作用。当然了实现了UIDynamicItem的对象也可以创建自己独特的行为,极其重要的UIView就已经这么做了。UIDynamicBehavior对象可以融合在一起生成一个新的综合行为的对象,它包含所有的行为对于一个给定的对象或一组对象。

一旦我们指定了动态对象的行为,我们就可以为他们提供一个UIDynamicAnimator实例(也就是物理引擎自身).它就可以自己运行根据不同的行为来计算不同对象间的相互影响。下图用来展示UIKit Dynamics世界中的概念图:

构建一个摆钟

我们回忆到大学的实验,最简单的牛顿物理实验就是一个摆钟。我们创建一个UIView来代表一个钟摆球:

1
2
3
4
5
6
7
UIView *ballBearing = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 40, 40)];
ballBearing.backgroundColor = [UIColor lightGrayColor];
ballBearing.layer.cornerRadius = 10;
ballBearing.layer.borderColor = [UIColor grayColor].CGColor;
ballBearing.layer.borderWidth = 2;
ballBearing.center = CGPointMake(200, 300);
[self.view addSubview:ballBearing];

现在我们可以为钟摆球添加一些行为,我们将会创建一个复合的行为集合(包含了很多基本行为)

1
UIDynamicBehavior *behavior = [[UIDynamicBehavior alloc] init];

接下来我们将开始添加我们需要模拟的行为,首先是重力行为:

1
2
3
UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:@[ballBearing]];
gravity.magnitude = 10;
[behavior addChildBehavior:gravity];

UIGravityBehavior代表了对象本身和地球之间的重力吸引力,它有一些属性允许你设置矢量的万有引力(就是重力系数magnitude和方向direction),我们这里增加了重力系数,但是保持重力的方向在y轴上面。

另外一个连结物的行为(attachment behavior)需要添加到钟摆球上面,它代表了绳子对它的牵引作用:

1
2
3
4
CGPoint anchor = ballBearing.center;
anchor.y -= 20;
UIAttachmentBehavior *attachment = [[UIAttachmentBeahavior alloc] initWithItem:ballBearing attachedToAnchor:anchor];
[behavior addChildBehavior:attachment];

UIAttachmentBehavior实例伴随一个动态的对象(一个锚点或者另外的对象),它们有具体的属性来控制绳子对它的行为 - 具体就是它的频繁性、阻尼和长度。默认的设置就是一个完全刚性的连接物(没有任何长度伸缩弹性的物体),刚性的连接物正是我们的钟摆球所需要的。

现在我们已经为钟摆球指定了一些行为了,此时我们创建物理引擎来控制这些行为,所以我们定了一个类的全局变量(iVar):UIDynamicAnimator *_animator;

1
2
_animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
[_animator addBehavior:behavior];

UIDynamicAnimator代表了模拟动态系统的物理引擎,在这里我们创建了它,并且指定了它参照的view(既指定宇宙空间),也添加了我们创建的行为.

到此为止,我们已经创建了第一个UIKit Dynamics系统,然而,如果我们运行app,没有发生我们所期望的,这是因为此时app是在静态的设备中,我们需要让我们的设备运动起来就可以看到view的动态行为了。

手势响应行为

我们需要为钟摆球添加一个手势操作,这样可以通过手势来摆弄钟摆球模型:

1
2
UIPanGestureRecognizer *gesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleBallBearingPan:)];
[ballBearing addGestureRecognizer:gesture];

下面来实现手势的具体行为操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (void)handleBallBearingPan:(UIPanGestureRecognizer *)recognizer {
// If we're starting the gesture then create a drag force
if (recognizer.state == UIGestureRecognizerStateBegan) {
if (_userDragBehavior) {
[_animator removeBehavior:_userDragBehavior];
}

_userDragBehavior = [[UIPushBehavior alloc] initWithItems:@[recognizer.view] mode:UIPushBehaviorModeContinuous];
[_animator addBehavior:_userDragBehavior];
}

// Set the force to be proportional to distance the gesture has moved
_userDragBehavior.pushDirection = CGVectorMake([recognizer translationInView:self].x / 10.f, 0);

// If we're finishing then cancel the behavior to 'let-go' of the ball
if (recognizer.state = UIGestureRecognizerStateEnded) {
[_animator removeBehavior:_userDragBehavior];
_userDragBehavior = nil;
}
}

UIPushBehavior代表一个简单的线性力量作用于对象上面。当力消失的时候,我们就回收作用在钟摆球上面的力,我们定义了一个类全局变量(iVar) UIPushBehavior *_userDragBehvior,这个变量可是在手势触发开始的时候赋值,需要记得把这个行为添加到dynamics animator中, 我们力的大小与水平位移成正比, 为了让钟摆球摇摆,我们需要在手势结束的时候删除掉push行为。

多个组合的钟摆球

下图就是牛顿钟摆球的一个模型:

为了在创造这些使用UIKit Dynamics,我们需要创建多个钟摆球,具体的方法可以参照上面所实现的,它们放置的控件需要留有一点空隙(具体的实现参见代码中详细).

另外我们需要添加一个新的行为来描述两个钟摆球的碰撞,我们有一个类的全局变量保存在一个数组中NSArray *_ballBearings;:

1
2
UICollisionBehavior *collision = [[UICollisionBehavior alloc] initWithObjects:_ballBearings];
[behavior addChildBehavior:collision];

这里我们使用了一个碰撞行为作用在模拟系统中的一些对象上面,碰撞行为还可以用于模型对象达到边界如视图边界,或任意的贝塞尔曲线路径的界限。

如果我们现在运行app,并且移动一个钟摆球,你将会发现钟摆并不像我们认为的方式展示,这是因为碰撞不是弹性的,我们需要为行为添加一个指定的类型来指定各种共享的属性:

1
2
3
4
5
6
UIDynamicItemBehavior *itemBehavior = [[UIDynamicItemBehavior alloc] initWithItems:_ballBearings];
// Elasticity governs the efficiency of the collisions
itemBehavior.elasticity = 1.0;
itemBehavior.allowsRotation = NO;
itemBehavior.resistance = 0.5;
[behavior addChildBehavior:itemBehavior];

我们使用UIDynamicItemBehavior来指定碰撞的弹性,同时也设置了其他的属性例如resistance(阻尼,空气产生的阻尼)和rotation(选择),如果我们运行旋转,我们可以指定角阻力, dynamic item 行为也可以设置线性和角速度,把它们添加到手势操作上面是十分有用的。

再次运行app,我们会发现,此时的行为就和我们期望的一样。也许你需要自己添加一些连接线

至此,代码部分就完成了,使用所有介绍的元素,可以一步到demo app中查看完整的代码.

总结

这篇文章对于UIKit Dynamics的介绍还只是停留在表面上,但是通过这里介绍的东西可以模拟出更加复杂的物理系统,这只是入门前的开胃菜而已,通过这个我们可以理解现实物理系统中的动作和互相作用。

本文翻译自:iOS7 Day-by-Day :: Day 0 :: UIKit Dynamics

文章目录
  1. 1. 物理世界
  2. 2. 构建一个摆钟
  3. 3. 手势响应行为
  4. 4. 多个组合的钟摆球
  5. 5. 总结
,