UIKit Dyanmics 是 UIKit 下的一個二維的物理引擎。
參考:
關鍵的幾個類:
UIDynamicBehavior 模擬對象的行為
UIDynamicAnimator 物理引擎
UIDynamicAnimator 自己可以根據不同的行為來計算不同對象間的相互影響。
使用方法總結:
- 用 UIDynamicBehavior 的子類對象設置不同物體各自的行為或者它們通用的行為,然后將它們添加到 UIDynamicBehavior 實例對象上,
- 將 UIDynamicBehavior 實例對象添加到 UIDynamicAnimator 物理引擎上。
Demo源碼:牛頓擺鐘
該 Demo 構建一個牛頓擺鐘模擬重力實驗。
SCBallBearingView
// **********************************************
#import <UIKit/UIKit.h>
/**
圓形鐘擺球模型
*/
@interface SCBallBearingView : UIView
@end
// **********************************************
#import "SCBallBearingView.h"
@import QuartzCore;
@implementation SCBallBearingView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// 初始化代碼
self.backgroundColor = [UIColor lightGrayColor];
self.layer.cornerRadius = MIN(CGRectGetHeight(frame), CGRectGetWidth(frame)) / 2.0;
self.layer.borderColor = [UIColor grayColor].CGColor;
self.layer.borderWidth = 2;
}
return self;
}
@end
?? SCNewtonsCradleView
// **********************************************
#import <UIKit/UIKit.h>
@interface SCNewtonsCradleView : UIView
@end
// **********************************************
#import "SCNewtonsCradleView.h"
#import "SCBallBearingView.h"
@implementation SCNewtonsCradleView {
NSArray *_ballBearings; // 鐘擺球容器
UIDynamicAnimator *_animator; // 用于控制行為的物理引擎
UIPushBehavior *_userDragBehavior; // 手勢的線性力量
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// 初始化代碼
[self createBallBearings];
[self applyDynamicBehaviors];
// 初始化時施加推力讓鐘擺球搖擺
_userDragBehavior = [[UIPushBehavior alloc] initWithItems:@[_ballBearings[0]] mode:UIPushBehaviorModeInstantaneous];
_userDragBehavior.pushDirection = CGVectorMake(-0.5, 0);
[_animator addBehavior:_userDragBehavior];
}
return self;
}
// 1.創建5個鐘擺球
- (void)createBallBearings
{
NSMutableArray *bbArray = [NSMutableArray array];
NSUInteger numberBalls = 5;
CGFloat ballSize = CGRectGetWidth(self.bounds) / (3.0 * (numberBalls - 1));
for (NSUInteger i=0; i<numberBalls; i++) {
// 鐘擺球需要設置得足夠小,使它們之間靜態擺放時仍留有間隙。
SCBallBearingView *bb = [[SCBallBearingView alloc] initWithFrame:CGRectMake(0, 0, ballSize - 1, ballSize - 1)];
// 把每個鐘擺球放到合適的位置
CGFloat x = CGRectGetWidth(self.bounds) / 3.0 + i * ballSize;
CGFloat y = CGRectGetHeight(self.bounds) / 2.0;
bb.center = CGPointMake(x, y);
// 為每個鐘擺球添加一個手勢操作,這樣可以通過手勢來擺弄鐘擺球模型:
UIPanGestureRecognizer *gesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleBallBearingPan:)];
[bb addGestureRecognizer:gesture];
// 把鐘擺球添加進數組中,并添加為當前頁面子視圖
[bbArray addObject:bb];
[self addSubview:bb];
}
_ballBearings = [NSArray arrayWithArray:bbArray];
}
#pragma mark - UIDynamics utility methods
// 2.添加動力行為
- (void)applyDynamicBehaviors
{
// 創建一個復合的行為集合 UIDynamicBehavior (可以包含很多基本行為)
UIDynamicBehavior *behavior = [[UIDynamicBehavior alloc] init];
// 將每個球軸承安裝到其樞軸點
for(id<UIDynamicItem> ballBearing in _ballBearings)
{
// 2.1 為每個鐘擺球添加連結物行為
UIDynamicBehavior *attachmentBehavior = [self createAttachmentBehaviorForBallBearing:ballBearing];
[behavior addChildBehavior:attachmentBehavior];
}
// 2.2 添加重力行為
[behavior addChildBehavior:[self createGravityBehaviorForObjects:_ballBearings]];
// 2.3 添加碰撞行為
[behavior addChildBehavior:[self createCollisionBehaviorForObjects:_ballBearings]];
// 2.4 指定碰撞的彈性
// 使用 UIDynamicItemBehavior 來指定碰撞的彈性。
// dynamic item 行為也可以設置線性和角速度,把它們添加到手勢操作上面是十分有用的。
UIDynamicItemBehavior *itemBehavior = [[UIDynamicItemBehavior alloc] initWithItems:_ballBearings];
// 設置了其他的屬性
itemBehavior.elasticity = 1.0; // 彈性
itemBehavior.allowsRotation = NO; // rotation(旋轉),運行旋轉時,可以指定角阻力
itemBehavior.resistance = 2.f; // resistance(阻尼,空氣產生的阻尼)
[behavior addChildBehavior:itemBehavior];
// 2.5 創建物理引擎動畫
// 創建物理引擎來控制這些行為,所以我們定了一個類的全局變量(iVar):UIDynamicAnimator *_animator;
// UIDynamicAnimator代表了模擬動態系統的物理引擎,在這里我們創建了它,并且指定了它參照的view(既指定宇宙空間),也添加了我們創建的行為.
_animator = [[UIDynamicAnimator alloc] initWithReferenceView:self];
// Add the composite behavior
[_animator addBehavior:behavior];
}
// 連結物的行為(attachment behavior)需要添加到鐘擺球上面,它代表了繩子對它的牽引作用:
- (UIDynamicBehavior *)createAttachmentBehaviorForBallBearing:(id<UIDynamicItem>)ballBearing
{
// UIAttachmentBehavior 實例伴隨一個動態的對象(一個錨點或者另外的對象),它們有具體的屬性來控制繩子對它的行為 - 具體就是它的頻繁性、阻尼和長度。默認的設置就是一個完全剛性的連接物(沒有任何長度伸縮彈性的物體),剛性的連接物正是我們的鐘擺球所需要的。
CGPoint anchor = ballBearing.center;
// 錨點在鐘擺球的正上方
anchor.y -= CGRectGetHeight(self.bounds) / 4.0;
// 在瞄點上方畫一個盒子. 這不是行為的一部分,只是為了視覺上很好看
UIView *blueBox = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
blueBox.backgroundColor = [UIColor blueColor];
blueBox.center = anchor;
[self addSubview:blueBox];
// 2.1 創建連結物行為
UIAttachmentBehavior *behavior = [[UIAttachmentBehavior alloc] initWithItem:ballBearing
attachedToAnchor:anchor];
return behavior;
}
- (UIDynamicBehavior *)createGravityBehaviorForObjects:(NSArray *)objects
{
// 2.2 創建重力行為
// UIGravityBehavior 代表了對象本身和地球之間的重力吸引力,它有一些屬性允許你設置矢量的萬有引力(就是重力系數magnitude和方向direction),我們這里增加了重力系數,但是保持重力的方向在y軸上面。
UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:objects];
gravity.magnitude = 10; // 設置重力系數
return gravity;
}
- (UIDynamicBehavior *)createCollisionBehaviorForObjects:(NSArray *)objects
{
// 2.3 創建碰撞行為
// 將碰撞行為作用在模擬系統中的所有對象上面。
// 碰撞行為還可以用于模型對象達到邊界如視圖邊界,或任意的貝塞爾曲線路徑的界限。
return [[UICollisionBehavior alloc] initWithItems:objects];
}
#pragma mark - UIGestureRecognizer target method
// 實現手勢的具體行為
// UIPushBehavior 代表一個簡單的線性力量作用于對象上面。當力消失的時候,我們就回收作用在鐘擺球上面的力,我們定義了一個類全局變量(iVar) UIPushBehavior *_userDragBehvior,這個變量可是在手勢觸發開始的時候賦值,需要記得把這個行為添加到dynamics animator中, 我們力的大小與水平位移成正比, 為了讓鐘擺球搖擺,我們需要在手勢結束的時候刪除掉push行為。
- (void)handleBallBearingPan:(UIPanGestureRecognizer *)recognizer
{
// 拖動手勢觸發時,創建一個拖動力
if (recognizer.state == UIGestureRecognizerStateBegan) {
if(_userDragBehavior) {
[_animator removeBehavior:_userDragBehavior];
}
// 將 UIPushBehavior 對象添加到 UIDynamicAnimator 物理引擎上
_userDragBehavior = [[UIPushBehavior alloc] initWithItems:@[recognizer.view] mode:UIPushBehaviorModeContinuous];
[_animator addBehavior:_userDragBehavior];
}
// 設置力的大小,與水平位移成正比
_userDragBehavior.pushDirection = CGVectorMake([recognizer translationInView:self].x / 10.f, 0);
// 拖動手勢結束時,回收作用在鐘擺球上面的力
if (recognizer.state == UIGestureRecognizerStateEnded) {
[_animator removeBehavior:_userDragBehavior];
_userDragBehavior = nil;
}
}
將牛頓鐘擺球視圖添加到主頁:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
_newtonsCradle = [[SCNewtonsCradleView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:_newtonsCradle];
}