iOS 物理引擎 UIKit Dynamics 的简单使用

概述

UIKit Dynamic是在iOS 7中引入的技术,隶属于UIKit框架,所以只要你引入的UIKit框架就可以利用它来实现一些物理方面的效果,比如重力、碰撞。UIDynamic使用起来比较简单,不需要太多数学方面的知识,使用它只需要几个步骤:

  • 1.创建一个物理模型仿真器UIDynamicAnimator,设置它作用的视图Reference View

  • 2.创建物理模型仿真行为UIDynamicBehavior,并添加元素UIDynamicItem

  • 3.将仿真行为添加到仿真器中。

就这么简单。写完了。

UIDynamicAnimator

UIDynamicAnimator是整个UIKit Dynamic的核心部分,他有一个他影响的viewReference View,所有的行为UIDynamicBehavior都是需要添加到这里。

UIDynamicBehavior

UIDynamicBehavior是我们通常所用到的物理表现形式,主要有6个动作行为:

  • UIGravityBehavior
    重力行为:用来代表重力,应该不用多说了。

  • UICollisionBehavior
    碰撞行为:可以让UIView之前产生碰撞的效果,或者让View与边界上产生碰撞

  • UISnapBehavior
    捕捉行为:他可以让你吸引各种View。

  • UIPushBehavior
    推动行为:这个就相当于你对所作用的view产生了一个推力,这个推力可以使持续的,也可以是瞬时的。

  • UIAttachmentBehavior
    附着行为:这个行为是将两个view连接在一起,或者使一个view连接到某个点。

  • UIDynamicItemBehavior
    动力元素行为:这个跟之前的都不太一样,这个类并不是代表某个特定的行为,应该说是一些物理单位?但是可以对behavior进行一些特定的调整。

UIDynamicItem

UIDynamicItem是动力的基本作用单位。

说了这么多,来点实际的,让我们先用起来。

Demo

构建环境 UIDynamicAnimator

首先,建立一个物理仿真器。

1
2
3
@interface ViewController ()
@property (nonatomic ,strong) UIDynamicAnimator *animator;
@end
1
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];

这里边的referenceView就是指所影响到的view,我们可以把它比作我们所创造的世界。

构建元素 UIDynamicItem

接下来,我们就创建一个作用元素。

1
2
3
@interface ViewController ()
@property (nonatomic ,strong) UIView *ballItem;
@end
1
2
3
4
5
6
7
self.ballItem = [[UIView alloc] initWithFrame:CGRectMake([UIScreen mainScreen].bounds.size.width/2-15, 0, 30, 30)];
self.ballItem.backgroundColor = [UIColor redColor];
self.ballItem.layer.borderWidth = 1;
self.ballItem.layer.borderColor = [UIColor clearColor].CGColor;
self.ballItem.layer.cornerRadius = 15;
self.ballItem.layer.masksToBounds = YES;
[self.view addSubview:self.ballItem];

我们创建了一个小球,我们把UIDynamicBehavior添加上去之后它就会像是现实世界中的小球一样了。

重力效果 UIGravityBehavior

接下来创建一个重力效果,然后将小球赋予这个效果,最后将它加到物理仿真器中。

1
2
UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:@[self.ballItem]];
[self.animator addBehavior:gravity];

这样我们就给我们的小球添加了一个重力作用,现实世界中我们的重力加速度g=9.8m/s2,在iOS设备中也遵循了这个数值,只不过我们不是m,而是像素点了。

logo

另外,也可以通过设置重力的大小、方向。这里的方向使用的是弧度制,并且遵循坐标系,即屏幕右侧方向为0、2π,下方为π/2,左侧方向为π,上方为3π/2。

1
2
3
UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:@[self.ballItem]];
gravity.magnitude = 10.0; //设置重力的大小
gravity.angle = M_PI_4; //设置重力的方向
logo

碰撞效果 UICollisionBehavior

创建一个碰撞效果和创建一个重力效果一样简单。直接初始化后加到小球上边后添加到仿真器中,效果就实现了。唯一的区别是碰撞效果需要设定碰撞的范围也就是translatesReferenceBoundsIntoBoundary,它默认是作用于当前view的,也就是self.view。当然你也可以使用

1
2
3
UICollisionBehavior *collison = [[UICollisionBehavior alloc] initWithItems:@[self.ballItem]];
collison.translatesReferenceBoundsIntoBoundary = YES;
[self.animator addBehavior:collison];
logo

我们还可以通过设置collisionMode来控制元素之间碰撞的模式。我们再添加一个蓝色的小球并把它赋予碰撞效果,然后来看一下不同collisionMode下他们的表现状态。

1
@property (nonatomic, readwrite) UICollisionBehaviorMode collisionMode;
1
2
3
4
5
typedef NS_OPTIONS(NSUInteger, UICollisionBehaviorMode) {
UICollisionBehaviorModeItems = 1 << 0, //元素之间碰撞
UICollisionBehaviorModeBoundaries = 1 << 1, //边界碰撞
UICollisionBehaviorModeEverything = NSUIntegerMax //碰撞所有
} NS_ENUM_AVAILABLE_IOS(7_0);

元素之间的碰撞

1
2
3
UICollisionBehavior *collison = [[UICollisionBehavior alloc] initWithItems:@[self.ballItem,self.anOtherBallItem]];
collison.collisionMode = UICollisionBehaviorModeItems;
[self.animator addBehavior:collison];
logo

边界碰撞

1
2
3
UICollisionBehavior *collison = [[UICollisionBehavior alloc] initWithItems:@[self.ballItem,self.anOtherBallItem]];
collison.collisionMode = UICollisionBehaviorModeBoundaries;
[self.animator addBehavior:collison];
logo

碰撞所有

1
2
3
4
UICollisionBehavior *collison = [[UICollisionBehavior alloc] initWithItems:@[self.ballItem,self.anOtherBallItem]];
collison.collisionMode = UICollisionBehaviorModeEverything;
[self.animator addBehavior:collison];
logo

可以看到,无论是那种情况,我们如果没有设置translatesReferenceBoundsIntoBoundary = YES的话都是不会与边界发生碰撞的,如果我们设置了translatesReferenceBoundsIntoBoundary之后就会出现这样的情况。

logo

当然,我们还可以通过setTranslatesReferenceBoundsIntoBoundaryWithInsets设定一个你想要范围。如果更复杂一点还可以使用addBoundaryWithIdentifier来添加边界。这里就不展开使用了。

碰撞行为中还有一些代理用来检测不同的元素之间的碰撞。

1
2
3
4
5
6
7
8
9
10
11
@protocol UICollisionBehaviorDelegate <NSObject>
@optional
// 当一个两个动态元素之间发生碰撞时调用
- (void)collisionBehavior:(UICollisionBehavior *)behavior beganContactForItem:(id <UIDynamicItem>)item1 withItem:(id <UIDynamicItem>)item2 atPoint:(CGPoint)p;
// 当一个两个动态元素之间碰撞结束时调用
- (void)collisionBehavior:(UICollisionBehavior *)behavior endedContactForItem:(id <UIDynamicItem>)item1 withItem:(id <UIDynamicItem>)item2;
// 当一个动态元素与边界发生碰撞时调用
- (void)collisionBehavior:(UICollisionBehavior*)behavior beganContactForItem:(id <UIDynamicItem>)item withBoundaryIdentifier:(nullable id <NSCopying>)identifier atPoint:(CGPoint)p;
// 当一个动态元素与边界碰撞结束时调用
- (void)collisionBehavior:(UICollisionBehavior*)behavior endedContactForItem:(id <UIDynamicItem>)item withBoundaryIdentifier:(nullable id <NSCopying>)identifier;
@end

捕捉效果 UISnapBehavior

捕捉效果就是使影响到的item捕捉到动作到达某个点,也可以理解为一种吸附力。到达指定的点后还有一个弹簧的效果,可以通过damping来调节弹簧的大小,范围为0.0~1.0,数值越大,弹性越小,默认的弹性大小为0.5。我们添加一个捕捉效果动作并且添加一个点击手势UITapGestureRecognizer

1
2
3
4
5
6
self.snap = [[UISnapBehavior alloc] initWithItem:self.ballItem snapToPoint:CGPointMake(WIDTH/2, HEIGHT/2)];
self.snap.damping = 0.5;
[self.animator addBehavior:self.snap];
UITapGestureRecognizer *tapGR = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGR:)];
[self.view addGestureRecognizer:tapGR];

在点击的响应事件当中,我们修改捕捉效果的snapPoint让小球到达我们点击的点。

1
2
3
4
5
6
- (void)tapGR:(UITapGestureRecognizer *)gesture
{
CGPoint point = [gesture locationInView:self.view];
self.snap.snapPoint = point;
}
logo

推力行为 UIPushBehavior

推力行为顾名思义,就是给元素一个推动力,这个推动的力可以是持续的,也可以是瞬间的。
推力的初始化时会加入一个mode:

1
2
3
4
typedef NS_ENUM(NSInteger, UIPushBehaviorMode) {
UIPushBehaviorModeContinuous, //持续的推力
UIPushBehaviorModeInstantaneous //瞬间的推力
} NS_ENUM_AVAILABLE_IOS(7_0);

持续推力

为了看的更明显一些,我给他们也加入了边框碰撞效果和弹性效果。并且为了使角度的参数看的更加明显,我用了小方块儿代替小球。

1
2
3
UIPushBehavior *push = [[UIPushBehavior alloc]initWithItems:@[self.ballItem] mode: UIPushBehaviorModeContinuous];
push.magnitude = 1; //推力大小
[self.animator addBehavior:push];
logo

瞬间推力

1
2
3
UIPushBehavior *push = [[UIPushBehavior alloc]initWithItems:@[self.ballItem] mode: UIPushBehaviorModeInstantaneous];
push.magnitude = 1; //推力大小
[self.animator addBehavior:push];
logo

推力方向

同时我们还可以通过向量或者角度来控制推力作用的方向,这一个属性基本上与重力效果 UIGravityBehavior差不多。接下来我们来看下推力方向的角度设置。

1
2
3
4
UIPushBehavior *push = [[UIPushBehavior alloc]initWithItems:@[self.ballItem] mode:UIPushBehaviorModeInstantaneous];
push.angle = M_PI_2; //推力角度
push.magnitude = 1; //推力大小
[self.animator addBehavior:push];
logo

偏移量

我们还可以通过选择力对元素的作用点来改变物体的运动状态,比如我将偏移量设置为(15,15)

1
2
3
4
5
UIPushBehavior *push = [[UIPushBehavior alloc]initWithItems:@[self.ballItem] mode:UIPushBehaviorModeInstantaneous];
push.angle = M_PI_2; //推力角度
push.magnitude = 1; //推力大小
[push setTargetOffsetFromCenter:UIOffsetMake(15, 15) forItem:self.ballItem];
[self.animator addBehavior:push];
logo

附着行为 UIAttachmentBehavior

附着行为,就是会以某个点或者以某个元素为作用点,距离此点或元素为一定距离的行为。
附着行为中可以设置链接的位置为点或者是某个其他元素,其中间的距离可以调节,连接的方式也可以分为刚性或者弹性两种情况,刚性就是两者之间用一根棒子连接起来,弹性就是两者之间好像是用橡皮筋链接起来。

以某个点为作用点

我们可以设置length属性来表示元素与点之间的距离。

1
2
3
UIAttachmentBehavior * attachmentBehavior = [[UIAttachmentBehavior alloc] initWithItem:self.ballItem attachedToAnchor:CGPointMake(WIDTH/2+30, 400)];
attachmentBehavior.length = 30;
[self.animator addBehavior:attachmentBehavior];
logo

以某个元素为作用点

我们先添加一个蓝色的小球作为作用的元素,为了让效果更加明显一点,我给边界添加了一个碰撞,并且只给红色的小球添加重力。

1
2
3
UIAttachmentBehavior *attachmentBehavior2 = [[UIAttachmentBehavior alloc] initWithItem:self.ballItem attachedToItem:self.ballItem2];
attachmentBehavior2.length = 100;
[self.animator addBehavior:attachmentBehavior2];
logo

动力元素行为 UIDynamicItemBehavior

动力元素行为比较特殊,他没有前边几个行为有那么明显的表象,但是他提供了一些自然界中存在的一些动力学现象,比如弹力、摩擦力、密度。。。和其他行为混合使用可以发挥出意想不到的效果。

弹性 elasticity

1
@property (readwrite, nonatomic) CGFloat elasticity;

主要用于碰撞行为中元素之间碰撞之后的弹力,比如设置之后就可以让落地的小球更像一个铅球或者是一个皮球。他的默认值为0.0,即没有弹力,范围是0.0~1.0,数值越大,弹性效果也就越大。

摩擦力 friction

1
@property (readwrite, nonatomic) CGFloat friction;

摩擦力作用于两个元素之间的摩擦。默认值为0.0,即没有摩擦,数值越高摩擦力越大。

密度 density

1
@property (readwrite, nonatomic) CGFloat density;

密度的大小会对元素的摩擦力、弹性、推力大小都有影响。默认值为1.0

线速度阻尼 resistance

1
@property (readwrite, nonatomic) CGFloat resistance;

用于处理动态元素的线速度阻尼大小。默认值是0.0,当数值为1.0时会受到非常大的阻力,运动中的元素会马上停止。

角速度阻尼 angularResistance

1
@property (readwrite, nonatomic) CGFloat angularResistance;

同理于线速度阻尼。

电荷 charge

1
@property (readwrite, nonatomic) CGFloat charge NS_AVAILABLE_IOS(9_0);

这个属性太专业了。。。不太懂,如果有大牛知道是什么用处还请指教。

是否固定 anchored

1
@property (nonatomic, getter = isAnchored) BOOL anchored NS_AVAILABLE_IOS(9_0);

固定元素,当元素被固定后仍然可以设置行为,行为会影响到元素只是不会使元素发生位移。比如在碰撞行为中,元素会被碰撞但是不会碰撞后发生位移。默认值为NO。

以上就是UIKit Dynamic的简单使用,仅作为个人学习笔记,如果有什么地方写的不对还请大神们批评指教。