iOS7建议我们创建的界面具有物理特性,而不只是像素的集合,可以响应触摸、手势、屏幕方向改变等事件,让用户与界面之间有更深入的交互,而不是像iOS6那样在软件界面上模仿现实世界的纹理而已。或许你会认为创建感觉上真实的界面比创建视觉上真实的界面要难的多。然而通过UIKit Dynamics 和 Motion Effects,这一切将变得非常简单。


•UIKit Dynamics是UIKit中的一个动力学引擎,通过它可以给界面添加重力、铰链连接,碰撞,悬挂等效果,让人感觉界面就是现实中存在的物体的。

开始编码吧

现在我们就从一些简单的小例子开始学习UIKit dynamic。

创建一个新的项目,在ViewController.m的viewDidLoad:中添加以下代码:

UIView* square = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
square.backgroundColor = [UIColor grayColor];
[self.view addSubview:square];

上面的代码在用户界面上添加了一个方块。运行项目可以看到:

尝试倾斜、旋转、摇动你的手机,额,好似什么都没发生。当往界面上添加view,它会根据你设置的frame而保持在对应的位置上。直到你给界面添加动力学效果!

添加重力

ViewController.m中,添加全局变量:

UIDynamicAnimator* _animator;
UIGravityBehavior* _gravity;

在viewDidLoad:最后进行初始化:

_animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
_gravity = [[UIGravityBehavior alloc] initWithItems:@[square]];
[_animator addBehavior:_gravity];

文章后面会解释代码,现在只管运行项目。你会看见方块开始向下慢慢加速运动,一直到掉出屏幕底部:

上面添加的代码,有两个动力学数据类型

•UIDynamicAnimator是UIKit physics engine(下文统一称作"动力学引擎"),记录着你添加到引擎中的行为(behavior),例如重力,并提供上下文(context)。当你创建animator实例的时候,需要传入一个view对象作为参照系。

•UIGravityBehavior创建重力行为模型,并且作用于一个或多个物体上。当你创建一个behavior实例,将它与一系列物体(一般是view)相关联,这样你就可以设置哪些物体会受到behavior所影响,在这里就是受重力影响。

除了UIGravityBehavior,还有UIAttachmentBehavior、UICollisionBehavior、UIPushBehavior、UISnapBehavior等(下文统称”行为”)。大多数行为都有很多配置属性,例如,重力行为允许你修改重力的角度和大小,试着修改这些属性,使得物体以不同的加速度往上、往两边或斜着下落。

在现实世界中,重力加速度约为9.8m/s2,根据牛顿第二定律,可以用以下公式计算出在重力影响下物体的位移量:

distance = 0.5 × g × time<span style="font-size: 7px;">2</span>

在UIKit Dynamics中,以上也公式成立,但是使用的单位不一样,用等量化的点(point,之后简写为p)来代替。即UI重力加速度定义为1000p/s2。当然我们并不需要深入去研究,只需要知道g越大,物体就会下落得越快。

设置边界

在上面的效果中,方块在掉出屏幕底部后,会继续做下落运动,只是我们再也看不到罢了。为了使其保持在屏幕之内,需要定义边界,在 ViewController.m中添加一个新的实例变量:

UICollisionBehavior* _collision;

在viewDidLoad:中添加下面代码:

_collision = [[UICollisionBehavior alloc]initWithItems:@[square]];
_collision.translatesReferenceBoundsIntoBoundary = YES;
[_animator addBehavior:_collision];

上面的代码创建了一个碰撞行为。并且将translatesReferenceBoundsIntoBoundary属性设置为YES,而不是明确地添加边界坐标。这样设置之后,将以参考系的边作为边界。运行,可以看到方块会与屏幕的边界发生碰撞,并经过反弹后才停下来。

处理碰撞

下一步你会添加一个固定的障碍物,方块下落时将会与其发生碰撞。在viewDidLoad中添加以下代码:

UIView* barrier = [[UIView alloc] initWithFrame:CGRectMake(0, 300, 130, 20)];
barrier.backgroundColor = [UIColor redColor];
[self.view addSubview:barrier];

运行项目,可以看到红色的障碍物在屏幕中间。不过,障碍物并不起作用,方块下落时直接穿过障碍物。

这并不是我们期望的效果,不过这提醒了我们:dynamics只会对绑定了行为的view起作用。看一下示意图:

在前面的代码中,UIDynamicAnimator绑定了用来确定坐标系统的参照系视图,然后添加了一个或多个行为到关联的物体上,大多数行为可以关联到不同的物体上,每一个物体也可以关联多个不同的行为。上面的示意图展现了现在现在app中的行为和它们的关联关系。所有的行为都没”识别出”障碍物。所以动力学引擎并没有识别它。


使物体响应碰撞

为了让方块与障碍物发生碰撞,找到初始化碰撞行为的代码,改成如下:

_collision = [[UICollisionBehavior alloc] initWithItems:@[square, barrier]];

碰撞物体需要知道可以与其发生作用的所有对象,因此,将障碍物添加到可以发生碰撞的物体列表中。运行,可以看到下面情况:


更新前面的示意图,可以看到,碰撞行为现在已同时关联了两个view:

然而,两个物体之间的交互仍然和期望有所出入,我们希望障碍物是固定不可以移动的,但当两个物体发生碰撞时,障碍物被撞飞了!

更奇怪的是,障碍物最后并没有像方块那样停止在屏幕底部,因为障碍物并没有与重力行为关联,这也解释了为什么在没与方块发生碰撞之前,障碍物没有发生移动。

看起来好像需要别的方式来解决这个问题。因为障碍物是不可动的,并不需要动力学引擎来识别它,不过这又怎样检测到碰撞呢?

看不见的边界和碰撞

将碰撞行为的初始化代码改回最初的样子:

_collision = [[UICollisionBehavior alloc] initWithItems:@[square]];

然后,添加边界:

// add a boundary that coincides with the top edge
CGPoint rightEdge = CGPointMake(barrier.frame.origin.x +barrier.frame.size.width, barrier.frame.origin.y);
[_collision addBoundaryWithIdentifier:@"barrier"fromPoint:barrier.frame.origintoPoint:rightEdge];

上面的代码添加了一个看不见的边界,与障碍物的上边界重合。障碍物对用户仍然是可见的,但对于动力学引擎则是不可见的。而新添加的边界则是对动力学引擎是可见的,对用户是不可见的。当方块下落时,看起来会与障碍物发生作用,但其实是撞击了不可动的边界线!

运行,可以看到期望的效果:

现在,UIKit Dynamics的作用变得更加清晰了:你可以用几行代码来实现动力学效果。接着将继续介绍动力学引擎交互时的一些细节。

碰撞的背后

每一个动力学行为都有一个action的block类型属性,来监测动画的执行。在viewDidLoad:中添加:

_collision.action =  ^{NSLog(@"%@, %@", NSStringFromCGAffineTransform(square.transform), NSStringFromCGPoint(square.center));
};

上面的代码会打印下落方块的center和transform属性。运行项目,你会看到Xcode控制台的log信息。在前400毫秒,log信息是这样的:

可以看到动力引擎正在改变方块的中心,当方块与障碍物发生碰撞,方块开始发生旋转,log信息输入变为这样:

可以看到动力引擎同时改变了方块的transform和position。

如果在动画过程中,我们通过代码改变方块的frame和transform属性,物体属性会被我们重写,也就是说,动力学控制过程中,我们不应该通过transform来缩放物体等。

动力行为赋予的对象是term items而不是view。因此,拥有动力行为的对象需要遵守UIDynamicItem协议:

@protocol UIDynamicItem <NSObject>@property (nonatomic, readwrite) CGPoint center;
@property (nonatomic, readonly) CGRect bounds;
@property (nonatomic, readwrite) CGAffineTransform transform;@end

UIDynamicItem对象允许动力学引擎读写center和transform属性,允许读取bounds属性。这允许在物体边缘创建碰撞的边界,以及计算物体的质量。这个协议说明动力学引擎不仅仅适用于UIView对象,其实还有另一个UIKit类UICollectionViewLayoutAttributes遵循这个协议。这允许力学引擎应用于collection view。

碰撞通知


接下来,我们看下怎么获取物体碰撞时的通知事件。

打开ViewController.m,添加遵从UICollisionBehaviorDelegate协议:

@interface ViewController () <UICollisionBehaviorDelegate>@end

在viewDidLoad中,在碰撞行为实例化之后,设置view controller作为代理对象:

_collision.collisionDelegate = self;

然后,添加代理方法:

- (void)collisionBehavior:(UICollisionBehavior *)behavior beganContactForItem:(id<UIDynamicItem>)item withBoundaryIdentifier:(id<NSCopying>)identifier atPoint:(CGPoint)p {NSLog(@"Boundary contact occurred - %@", identifier);
}

这个代理方法在碰撞发生时执行,并在控制台打印log信息。为了避免控制台因太多信息而杂乱不堪,移除前面添加的_collision.action打印信息。运行,可以看:

在log信息中,可以看到方块与identifier为barrier的边界发生碰撞。这是前面添加的不可见边界。identifier为null的是参考系的边界。或许在反弹时添加一点视觉效果会更加有趣,在打印log信息后面添加:

UIView* view = (UIView*)item;
view.backgroundColor = [UIColor yellowColor];
[UIView animateWithDuration:0.3 animations:^{view.backgroundColor = [UIColor grayColor];
}];

上面的代码改变碰撞时物体的颜色为黄色,并变回灰色:

目前为止,都是动力学引擎自动设置物体的物理属性(例如质量和弹性)。接下来我们看下怎么通过UIDynamicItemBehavior类来自己控制这些物理属性。

配置物体属性

在viewDidLoad的最后,添加下面代码:

UIDynamicItemBehavior* itemBehaviour = [[UIDynamicItemBehavior alloc] initWithItems:@[square]];
itemBehaviour.elasticity = 0.6;
[_animator addBehavior:itemBehaviour];

上面的代码创建一个item行为,与方块关联。然后将行为添加到animator。elasticity属性控制了物体的弹性。1.0代表完全弹性碰撞。也就是,碰撞过程中没有能量和速度损耗。这里设置elasticity的值为0.6,表示方块会在每次碰撞后速度都会减少。

运行项目:

在上面的代码中,只是改变了物体的弹性。而行为对象还有很多可以设置的属性:

elasticity(弹性系数) – 决定了碰撞的弹性程度,比如碰撞时物体的弹性。

friction(摩擦系数) – 决定了沿接触面滑动时的摩擦力大小。

density(密度) – 跟 size 结合使用,来计算物体的总质量。质量越大,物体加速或减速就越困难。

resistance(阻力) – 决定线性移动的阻力大小,这根摩擦系数不同,摩擦系数只作用于滑动运动。

angularResistance(转动阻力) – 决定转动运动的阻力大小。

allowsRotation(允许旋转) – 这个属性很有意思,它在真实的物理世界没有对应的模型。设置这个属性为 NO 物体就完全不会转动,无力受到多大的转动力。

动态添加行为

目前,我们的应用设置系统的所有行为,然后由力学引擎处理系统的物理行为,直至所有物体静止。现在,看下如何动态添加或删除行为。

打开 ViewController.m 并添加如下实例变量:

BOOL _firstContact;

添加下面的代码到碰撞代理方法collisionBehavior:beganContactForItem:withBoundaryIdentifier:atPoint: 的末尾:

if (!_firstContact)
{_firstContact = YES;UIView* square = [[UIView alloc] initWithFrame:CGRectMake(30, 0, 100, 100)];square.backgroundColor = [UIColor grayColor];[self.view addSubview:square];[_collision addItem:square];[_gravity addItem:square];UIAttachmentBehavior* attach = [[UIAttachmentBehavior alloc] initWithItem:viewattachedToItem:square];[_animator addBehavior:attach];
}

上面的代码检测到方块和障碍物的第一次接触时,创建第二个方块并添加到碰撞和重力行为中。此外,设置了一个吸附行为,实现两个物体之间加入虚拟的弹簧的效果。

运行应用,当原方块撞到障碍物时,会看到一个新的方块出现

虽然两个方块看起来被连接到一起,但是因为没有在屏幕上画线条或是弹簧,你并不会看到视觉上的联系。

参考文献:https://www.raywenderlich.com/50197/uikit-dynamics-tutorial

本文链接:http://blog.csdn.net/dolacmeng/article/details/52223164

iOS中的动力学:Dynamics【1】相关推荐

  1. iphone smtp服务器没有响应,电子邮件卡在iPhone或iPad上的发件箱?如何修复iOS中的未发送邮件 | MOS86...

    您曾经在iOS中发送电子邮件,只能将信息卡在iPhone,iPad或iPod touch的邮件应用发件箱中?你知道这是什么时候发生的,因为在iOS的Mail应用程序的底部,状态栏在iOS中显示1个未发 ...

  2. mui ios中form表单中点击输入框头部导航栏被推起及ios中form表单中同时存在日期选择及输入框时,日历选择页面错乱bug...

    一.ios header导航栏被推起解决方法 1 设置弹出软键盘时自动改变webview的高度 plus.webview.currentWebview().setStyle({ softinputMo ...

  3. iOS中UISearchBar(搜索框)使用总结

    2019独角兽企业重金招聘Python工程师标准>>> iOS中UISearchBar(搜索框)使用总结 初始化:UISearchBar继承于UIView,我们可以像创建View那样 ...

  4. iOS中几种数据持久化方案总结

    概论 所谓的持久化,就是将数据保存到硬盘中,使得在应用程序或机器重启后可以继续访问之前保存的数据.在iOS开发中,有很多数据持久化的方案,接下来我将尝试着介绍一下5种方案: plist文件(属性列表) ...

  5. iOS中关于NSTimer使用知多少

    看到这个标题,你可能会想NSTimer不就是计时器吗,谁不会用,不就是一个能够定时的完成任务的东西吗? 我想说你知道NSTimer会retain你添加调用方法的对象吗?你知道NSTimer是要加到ru ...

  6. iOS中JS 与OC的交互(JavaScriptCore.framework)

    iOS中实现js与oc的交互,目前网上也有不少流行的开源解决方案: 如:react native 当然一些轻量级的任务使用系统提供的UIWebView 以及JavaScriptCore.framewo ...

  7. 在iOS中使用tableView

    为什么80%的码农都做不了架构师?>>>    UITableView是iOS中最常用的控件了,所以使用起来也很简单. ViewContoller.h 文件 (继承UITableVi ...

  8. 关于ios中编译ffmpeg0.9.2库

    很多朋友在问如何在ios中编译ffmpeg库,虽说网上的教程很多,但是大部分都说按其操作,最后编译总是不成功,正好我最近的项目要用到ffmpeg,所以就再次编译了,同时在这里记下,方便需要参考的朋友. ...

  9. iOS中引用计数内存管理机制分析

    在 iOS 中引用计数是内存的管理方式,虽然在 iOS5 版本中,已经支持了自动引用计数管理模式,但理解它的运行方式有助于我们了解程序的运行原理,有助于 debug 程序. 操作系统的内存管理分成堆和 ...

最新文章

  1. Facebook 开源标准卷积替代方案 OctConv
  2. 利用SQL建立数据库对象
  3. 迪普工业以太网交换机产品线
  4. 什么是云服务举例说明_云服务是什么功能
  5. js向jsp传中文出现乱码的解决方法
  6. mysql常见的错误码
  7. Mac 编译报错 symbol(s) not found for
  8. 超出了GC开销限制– Java堆分析
  9. c++继承父类的子类,如何调用父类的同名函数?
  10. LeetCode 1553. 吃掉 N 个橘子的最少天数(BFS)
  11. Postgres外部表示例
  12. 程序闪退崩溃的几种原因
  13. VueScan Pro for Mac(万能扫描仪驱动程序)
  14. VS2005制作安装包
  15. PS(Photo Shop Cs6)批量调整图片大小
  16. 小爱同学脱离局域网远程控制开关?
  17. oracle的floor用法,oracle ceil floor 函数的用法
  18. linux命令行下的BT软件
  19. 厦大2021期中考试
  20. 寒假的一点笔记《123速通》

热门文章

  1. Geant4采用make和cmake编译运行geant4自带例子的方法
  2. 我的JavaScript学习笔记
  3. instanceof, isinstance,isAssignableFrom的区别
  4. iOS-查询数据库--指定数据表中的当前数据行的总数量
  5. 在ASP.NET中跟踪和恢复大文件下载
  6. .Net Framework 3.0 概述
  7. git管理大项目或者大文件
  8. windows版本下使用xdebug
  9. 高性能Mysql主从架构的复制原理及配置详解
  10. 存储方式与介质对性能的影响