iOS中UIDynamic物理仿真詳解

本文中所有代碼演示均有GitHub源碼姻僧,點(diǎn)擊下載

UIDynamic簡介

  • 簡介:

    • UIKit動力學(xué)最大的特點(diǎn)是將現(xiàn)實(shí)世界動力驅(qū)動的動畫引入了UIKit,比如動力蒲牧,鉸鏈連接撇贺,碰撞,懸掛等效果冰抢,即將2D物理引擎引入了UIKit松嘶。
    • 注意:UIKit動力學(xué)的引入,并不是為了替代CA或者UIView動畫挎扰,在絕大多數(shù)情況下CA或者UIView動畫仍然是最有方案翠订,只有在需要引入逼真的交互設(shè)計(jì)的時(shí)候,才需要使用UIKit動力學(xué)它是作為現(xiàn)有交互設(shè)計(jì)和實(shí)現(xiàn)的一種補(bǔ)充遵倦。
  • 其他2D仿真引擎:

    • BOX2D:C語言框架尽超,免費(fèi)
    • Chipmunk:C語言框架免費(fèi),其他版本收費(fèi)

UIDynamic中的三個(gè)重要概念

  • Dynamic Animator:動畫者梧躺,為動力學(xué)元素提供物理學(xué)相關(guān)的能力及動畫似谁,同時(shí)為這些元素提供相關(guān)的上下文,是動力學(xué)元素與底層iOS物理引擎之間的中介,將Behavior對象添加到Animator即可實(shí)現(xiàn)動力仿真巩踏。

  • Dynamic Animator Item:動力學(xué)元素秃诵,是任何遵守了UIDynamic協(xié)議的對象,從iOS7開始塞琼,UIView和UICollectionViewLayoutAttributes默認(rèn)實(shí)現(xiàn)協(xié)議菠净,如果自定義對象實(shí)現(xiàn)了該協(xié)議,即可通過Dynamic Animator實(shí)現(xiàn)物理仿真屈梁。

  • UIDynamicBehavior:仿真行為嗤练,是動力學(xué)行為的父類榛了,基本的動力學(xué)行為類UIGravityBehavior在讶、UICollisionBehavior、UIAttachmentBehavior霜大、UISnapBehavior构哺、UIPushbehavior以及UIDynamicItemBehavior均繼承自該父類。

項(xiàng)目搭建演練

  • 模擬重力體驗(yàn)物理仿真效果

  • 要使用物理仿真战坤,最基本的使用步驟是:

    • 1> 要有一個(gè) 仿真者[UIDynamicAnimator] 用來仿真所有的物理行為
    • 2> 要有物理 仿真行為[如重力UIGravity] 用來模擬重力的行為
    • 3> 將物理仿真行為添加給仿真者實(shí)現(xiàn)仿真效果曙强。
  • 第一種情況——重力仿真

    // 1. 誰來仿真?UIDynamicAnimator來負(fù)責(zé)仿真
    UIDynamicAnimator *animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
    
    // 2. 仿真?zhèn)€什么動作途茫?自由落體
    UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:@[view, redView]];
    
    // 3. 開始仿真
    [animator addBehavior:gravity];
    
    
  • 重力仿真效果圖</br>

    01重力效果無邊界檢測.gif

  • 第二種情況——增加邊緣檢測

    • 默認(rèn)情況下沒有任何阻擋控件直接掉出屏幕碟嘴,可以通過添加邊緣檢測行為防止掉出。
    // 3. 碰撞檢測
    UICollisionBehavior *collision = [[UICollisionBehavior alloc] initWithItems:@[view, redView]];
    // 設(shè)置不要出邊界囊卜,碰到邊界會被反彈
    collision.translatesReferenceBoundsIntoBoundary = YES;
    
    // 4. 開始仿真
    [animator addBehavior:collision];
    
  • 增加邊緣檢測效果圖</br>

02重力有邊界.gif
  • 第三種情況——旋轉(zhuǎn)
    • 讓控件旋轉(zhuǎn)45°后娜扇,控件并不會倒下,因?yàn)榭丶闹匦木驮?5°的那條線上栅组。
    • 如果修改為別的角度就會倒下
    view.transform = CGAffineTransformMakeRotation(M_PI_4);
    
  • 旋轉(zhuǎn)效果圖</br>
03旋轉(zhuǎn).gif
  • 第四種情況——碰撞
  • 再增加一個(gè)紅色的控件的時(shí)候就會發(fā)生碰撞的效果雀瓢。
  • 碰撞效果圖</br>

項(xiàng)目框架搭建

一、結(jié)構(gòu)分析

  • 為了演示其他的幾種行為效果玉掸,案例中需要用到

    • UINavigationController[導(dǎo)航控制器]刃麸,根控制器為列表控制器
    • UITableViewController[列表控制器],用來展示所有的行為列表
    • UIViewController[普通控制器]司浪,用來演示各種不同行為的效果
  • 在顯示各種行為的普通控制器中有2個(gè)共同點(diǎn):

    • 相同的背景效果
    • 都有一個(gè)小方塊
  • 所以為了避免每個(gè)行為都要寫一個(gè)控制器泊业,然后寫對應(yīng)的背景及方塊圖片代碼,就抽出一個(gè)示例控制器啊易,用來顯示所有的行為效果

    • 只不過示例控制器要加載和顯示的view吁伺,要根據(jù)要展示的行為去加載不同的view(多態(tài)的合理運(yùn)用)

二、代碼實(shí)現(xiàn)

1> 列表控制器
  • 第一步加載顯示導(dǎo)航控制器及列表控制器
    • 通過屬性列表或者數(shù)據(jù)源的方式加載所有的行為名詞
    _dynamicArr = @[@"吸附行為", @"推動行為", @"剛性附著行為", @"彈性附著行為", @"碰撞檢測"];
    
    • 通過給組尾設(shè)置一個(gè)空的view來隱藏多余行
    self.tableView.tableFooterView = [[UIView alloc] init];
    
    • 實(shí)現(xiàn)數(shù)據(jù)源方法顯示出來
    #pragma mark - 數(shù)據(jù)源方法
        // 幾行
        - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    
            return _dynamicArr.count;
        }
    
        // 每行的具體內(nèi)容
        - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
            // 1. 設(shè)置可重用標(biāo)識符
            static NSString *ID = @"cell";
    
            // 2. 根據(jù)可重用標(biāo)識符去tableView 緩存區(qū)去取
            UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    
            // 3. 設(shè)置每行cell 的文字
            cell.textLabel.text = _functions[indexPath.row];
    
            return cell;
    

}
```

  • 列表控制器效果圖</br>
05行為列表.png
2> 跳轉(zhuǎn)到演示控制器
  • 實(shí)現(xiàn)代理方法认罩,實(shí)現(xiàn)跳轉(zhuǎn)
  • 在跳轉(zhuǎn)的時(shí)候?qū)⑺饕癱ell的標(biāo)題傳過去
    #pragma mark - 代理方法
    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

    // 1. 實(shí)例化一個(gè)仿真管理器
    WPFDemoController *demoVc = [[WPFDemoController alloc] init];

    // 2. 設(shè)置標(biāo)題
    demoVc.title = _dynamicArr[indexPath.row];

    // 3. 傳遞功能類型
    demoVc.function = (int)indexPath.row;

    // 4. 跳轉(zhuǎn)界面
    [self.navigationController pushViewController:demoVc animated:YES];
    }
3> 演示控制器根據(jù)索引去加載不同的view
  • 做一個(gè)基本的view只用來設(shè)置背景及方塊圖片及仿真者箱蝠,其他的view在此基礎(chǔ)上添加功能,新建類 WPFBaseView
// 重寫其initWithFrame 方法,設(shè)置基本信息
- (instancetype)initWithFrame:(CGRect)frame {

    if (self = [super initWithFrame:frame]) {
        // 以平鋪的方式設(shè)置背景圖片
        self.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"BackgroundTile"]];

        // 設(shè)置方塊
        UIImageView *boxView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"Box1"]];
        boxView.center = CGPointMake(200, 220);
        [self addSubview:boxView];
        self.boxView = boxView;

        // 初始化仿真者
        UIDynamicAnimator *animator = [[UIDynamicAnimator alloc] initWithReferenceView:self];

        self.animator = animator;
    }
    return self;
}
  • WPFDemoController:根據(jù)傳入的索引去判斷加載那個(gè)行為的view
// 在此之前要先在頭文件中定義枚舉類型
- (void)viewDidLoad {
    [super viewDidLoad];

    // 新建一個(gè)空的baseView
    WPFBaseView *baseView = nil;

    // 根據(jù)不同的功能類型選擇不同的視圖
    // 運(yùn)用了多態(tài)
    switch (self.function) {
        case kDemoFunctionSnap:
            baseView = [[WPFSnapView alloc] init];
            break;

        case kDemoFunctionPush:
            baseView = [[WPFPushView alloc] init];
            break;

        case kDemoFunctionAttachment:
            baseView = [[WPFAttachmentView alloc] init];
            break;

        case kDemoFunctionSpring:
            baseView = [[WPFSpringView alloc] init];
            break;

        case kDemoFunctionCollision:
            baseView = [[WPFCollisionView alloc] init];
            break;

        default:
            break;
    }

    baseView.frame = self.view.bounds;

    [self.view addSubview:baseView];

}

  • 加載不同view的效果</br>
06根據(jù)不同的行為去加載view.gif

吸附行為:WPFSnapView

  • UISnapBehavior吸附行為
  • 在點(diǎn)擊屏幕的時(shí)候獲取觸摸點(diǎn)
  • 需要在創(chuàng)建吸附行為的時(shí)候指定要吸附的位置
  • 創(chuàng)建好之后將吸附行為添加到仿真者上
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

    // 0. 觸摸之前要清零之前的吸附事件宦搬,否則動作越來越小
    [self.animator removeAllBehaviors];

    // 1. 獲取觸摸對象
    UITouch *touch = [touches anyObject];

    // 2. 獲取觸摸點(diǎn)
    CGPoint loc = [touch locationInView:self];

    // 3 添加吸附事件
    UISnapBehavior *snap = [[UISnapBehavior alloc] initWithItem:self.boxView snapToPoint:loc];

    // 改變震動幅度牙瓢,0表示振幅最大,1振幅最小
    snap.damping = 0.5;

    // 4. 將吸附事件添加到仿真者行為中
    [self.animator addBehavior:snap];
}
  • 吸附行為效果圖</br>
07吸附行為.gif

推動行為:WPFPushView

  • UIPushBehavior推動行為

  • 介紹

    • 推行為可以為一個(gè)視圖施加一個(gè)作用力间校,該力可以是持續(xù)的矾克,也可以是一次性的

    • 可以設(shè)置力的大小,方向和作用點(diǎn)等信息

    • 屬性:

      • mode: 推動類型(一次性推動或是持續(xù)推送)
      • active: 是否激活憔足,如果是一次性推動胁附,需要激活
      • angle: 推動角度
      • 推動力量
  • 實(shí)例化推行為

  • 通過拖拽手勢獲取起始點(diǎn)及其他狀態(tài)的點(diǎn)

  • 設(shè)置全局變量

@interface WPFPushView ()
{
    UIImageView *_smallView;    // 顯示在第一個(gè)觸摸點(diǎn)位置的圖片框
    UIPushBehavior *_push;      // 推動的行為
    CGPoint _firstPoint;        // 手指點(diǎn)擊的第一個(gè)點(diǎn)
    CGPoint _currentPoint;      // 當(dāng)前觸摸點(diǎn)
}
@end
  • 推行為的創(chuàng)建
// 重寫init 方法
- (instancetype)init {

    if (self = [super init]) {

        // 1. 添加藍(lán)色view
        UIView *blueView = [[UIView alloc] initWithFrame:CGRectMake(150, 300, 20, 20)];
        blueView.backgroundColor = [UIColor blueColor];
        [self addSubview:blueView];



        // 2. 添加圖片框,拖拽起點(diǎn)
        UIImageView *smallView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"AttachmentPoint_Mask"]];
        // 該圖片框默認(rèn)是隱藏的滓彰,在觸摸屏幕的時(shí)候再顯示出來
        smallView.hidden = YES;
        [self addSubview:smallView];
        // 建立全局關(guān)系
        _smallView = smallView;

        // 3. 添加推動行為
        UIPushBehavior *push = [[UIPushBehavior alloc] initWithItems:@[self.boxView] mode:UIPushBehaviorModeInstantaneous];
        [self.animator addBehavior:push];
        _push = push;


        // 4. 增加碰撞檢測
        UICollisionBehavior *collision = [[UICollisionBehavior alloc] initWithItems:@[blueView, self.boxView]];
        collision.translatesReferenceBoundsIntoBoundary = YES;
        [self.animator addBehavior:collision];

        // 5. 添加拖拽手勢
        UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panAction:)];
        [self addGestureRecognizer:pan];

    }

    return self;
}
  • 通過拖拽手勢根據(jù)不同狀態(tài)去確定力的方向和大小
    // 監(jiān)聽開始拖拽的方法
- (void)panAction:(UIPanGestureRecognizer *)pan {

    // 如果是剛開始拖拽控妻,則設(shè)置起點(diǎn)處的小圓球
    if (pan.state == UIGestureRecognizerStateBegan) {

        _firstPoint = [pan locationInView:self];
        _smallView.center = _firstPoint;
        _smallView.hidden = NO;


    // 如果當(dāng)前拖拽行為正在移動
    } else if (pan.state == UIGestureRecognizerStateChanged) {

        _currentPoint = [pan locationInView:self];

        // 重繪當(dāng)前頁面
        [self setNeedsDisplay];

    // 如果當(dāng)前拖拽行為結(jié)束
    } else if (pan.state == UIGestureRecognizerStateEnded){

        // 1. 計(jì)算偏移量
        CGPoint offset = CGPointMake(_currentPoint.x - _firstPoint.x, _currentPoint.y - _firstPoint.y);

        // 2. 計(jì)算角度
        CGFloat angle = atan(offset.y / offset.x);

        if (_currentPoint.x > _firstPoint.x) {
            angle = angle - M_PI;
        }
        _push.angle = angle;

        // 3. 計(jì)算距離
        CGFloat distance = hypot(offset.y, offset.x);

        // 4. 設(shè)置推動的力度,與線的長度成正比
        _push.magnitude = directtion / 10;


        // 5. 使單次推行為有效
        _push.active = YES;

        // 6. 將拖拽的線隱藏
        _firstPoint = CGPointZero;
        _currentPoint = CGPointZero;

        // 7. 將起點(diǎn)的小圓隱藏
        _smallView.hidden = YES;

        // 8. 進(jìn)行重繪
        [self setNeedsDisplay];
    }
}
  • 設(shè)置劃線操作
- (void)drawRect:(CGRect)rect {

    // 1. 開啟上下文對象
    CGContextRef ref = UIGraphicsGetCurrentContext();

    // 2. 獲取路徑對象
    UIBezierPath *path = [UIBezierPath bezierPath];

    // 3. 劃線
    [path moveToPoint:_firstPoint];
    [path addLineToPoint:_currentPoint];
    CGContextAddPath(ref, path.CGPath);

    // 4. 設(shè)置線寬
    path.lineWidth = 7;

    // 5. 線的顏色
    [[UIColor greenColor] setStroke];

    // 6. 渲染
    [path stroke];

}
  • 推行為效果圖</br>
08推行為.gif

剛性附著行為:WPFAttachmentView

  • 簡介:

    • 附著行為是描述一個(gè)視圖與一個(gè)錨點(diǎn)或者另一個(gè)視圖相連接的情況
    • 附著行為描述的是兩點(diǎn)之間的連接情況揭绑,可以模擬剛性或者彈性連接
    • 在多個(gè)物理鍵設(shè)定多個(gè)UIAttachment弓候,可以模擬多物體連接。
  • 屬性

    • attachedBehaviorType: 連接類型(連接到錨點(diǎn)或視圖)
    • items: 連接到視圖數(shù)組
    • anchorPoint: 連接錨點(diǎn)
    • length: 距離連接錨點(diǎn)的距離
注意: 只要設(shè)置了以下兩個(gè)屬性他匪,即為彈性連接
  • damping: 振幅大小

  • frequency: 震動頻率

  • 設(shè)置全局變量

@interface WPFPushView ()
{
    // 附著點(diǎn)圖片框
    UIImageView *_anchorImgView;

    // 參考點(diǎn)圖片框(boxView 內(nèi)部)
    UIImageView *_offsetImgView;
}
@end
  • 創(chuàng)建附著行為
- (instancetype)init {
    if (self = [super init]) {

        // 1. 設(shè)置boxView 的中心點(diǎn)
        self.boxView.center = CGPointMake(200, 200);

        // 2. 添加附著點(diǎn)
        CGPoint anchorPoint = CGPointMake(200, 100);
        UIOffset offset = UIOffsetMake(20, 20);

        // 3. 添加附著行為
        UIAttachmentBehavior *attachment = [[UIAttachmentBehavior alloc] initWithItem:self.boxView offsetFromCenter:offset attachedToAnchor:anchorPoint];

        [self.animator addBehavior:attachment];
        self.attachment = attachment;

        // 4. 設(shè)置附著點(diǎn)圖片(即直桿與被拖拽圖片的連接點(diǎn))
        UIImage *image = [UIImage imageNamed:@"AttachmentPoint_Mask"];
        UIImageView *anchorImgView = [[UIImageView alloc] initWithImage:image];
        anchorImgView.center = anchorPoint;

        [self addSubview:anchorImgView];
        _anchorImgView = anchorImgView;

        // 3. 設(shè)置參考點(diǎn)
        _offsetImgView = [[UIImageView alloc] initWithImage:image];

        CGFloat x = self.boxView.bounds.size.width * 0.5 + offset.horizontal;
        CGFloat y = self.boxView.bounds.size.height * 0.5 + offset.vertical;
        _offsetImgView.center = CGPointMake(x, y);
        [self.boxView addSubview:_offsetImgView];

        // 4. 增加拖拽手勢
        UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panAction:)];
        [self addGestureRecognizer:pan];
    }
    return self;
}
  • 添加拖拽手勢菇存,在拖拽手勢移動的時(shí)候根據(jù)附著點(diǎn)及其軸點(diǎn)去繪制線段
    • 通過兩個(gè)屬性保存附著點(diǎn),及軸點(diǎn)
// 拖拽的時(shí)候會調(diào)用的方法
- (void)panAction:(UIPanGestureRecognizer *)pan {

    // 1. 獲取觸摸點(diǎn)
    CGPoint loc = [pan locationInView:self];

    // 2. 修改附著行為的附著點(diǎn)
    _anchorImgView.center = loc;
    self.attachment.anchorPoint = loc;

    // 3. 進(jìn)行重繪
    [self setNeedsDisplay];

}
  • 在繪制的時(shí)候需要注意將圖片框的軸點(diǎn)進(jìn)行坐標(biāo)轉(zhuǎn)換
- (void)drawRect:(CGRect)rect {
    // 1.獲取圖形上下文
    CGContextRef context = UIGraphicsGetCurrentContext();

    // 2.設(shè)置路徑起點(diǎn)
    CGContextMoveToPoint(context, _anchorImage.center.x, _anchorImage.center.y);

    // 2.2設(shè)置路徑畫線的點(diǎn)邦蜜,注意需要將軸點(diǎn)的坐標(biāo)進(jìn)行轉(zhuǎn)換
    // 使得兩個(gè)點(diǎn)的坐標(biāo)位于同一個(gè)坐標(biāo)系下
    // addline
    // 去偏移點(diǎn)相對于父視圖的坐標(biāo)
    CGPoint p = [self convertPoint:_offsetImage.center     fromView:self.box];
    CGContextAddLineToPoint(context, p.x, p.y);

    // 2.3設(shè)置虛線樣式
    CGFloat lengths[] = {10.0f, 8.0f};
    CGContextSetLineDash(context, 0.0, lengths, 2);

    // 2.4設(shè)置線寬
    CGContextSetLineWidth(context, 5.0f);

    // 3.渲染依鸥,繪制路徑
    CGContextDrawPath(context, kCGPathStroke);
}
  • 剛性附著行為效果圖</br>
    • 中心點(diǎn)沒有偏移</br>
09剛性附著行為.gif

* 中心點(diǎn)偏移</br>

09剛性附著行為2.gif

彈性附著行為:WPFSpringView

  • 彈性附著行為與剛性附著行為類似,只需要設(shè)置兩個(gè)屬性就好了悼沈。
// 振幅
self.attachment.damping = 0.1f;
// 頻率
self.attachment.frequency = 1.0f;
  • 彈性附著行為的view只需要繼承剛性附著行為就可以了贱迟。
// WPFAttachView是剛性附著行為的view,WPFSpringView為彈性附著行為的view
@interface WPFSpringView : WPFAttachView
  • 但是需要在后面需要修改彈性附著行為的效果井辆,所以要將剛性附著行為內(nèi)部的附著行為暴露在.h文件中
@property (nonatomic, weak) UIAttachmentBehavior *attachment;
  • 1.只設(shè)置了振幅和頻率的效果</br>
// 振幅
self.attachment.damping = 0.1f;
// 頻率
self.attachment.frequency = 1.0f;
10彈性附著行為.gif
  • 2.通過KVO監(jiān)聽方塊的中心點(diǎn)的變化关筒,實(shí)時(shí)去更新繪圖后的效果
// KVO監(jiān)聽boxcenter的改變
[self.box addObserver:self forKeyPath:@"center" options:NSKeyValueObservingOptionNew context:nil];
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {

        [self setNeedsDisplay];

    }
10彈性附著行為2.gif
  • 3.增加了重力后的效果</br>
// 添加重力
UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:@[self.box]];
[self.animator addBehavior:gravity];
10彈性附著行為3.gif
10彈性附著行為4.gif
  • 4.添加了碰撞檢測后的效果
// 添加碰撞檢測
UICollisionBehavior *collision = [[UICollisionBehavior alloc] initWithItems:@[self.box]];
collision.translatesReferenceBoundsIntoBoundary = YES;
[self.animator addBehavior:collision];
10彈性附著行為5.gif

碰撞檢測

  • 在碰撞界面添加一個(gè)紅色的view
  • 1.紅色的view也添加到碰撞檢測行為中</br>
// 增加一個(gè)紅色的條狀view
UIView *redV = [[UIView alloc] initWithFrame:CGRectMake(0, 400, 180, 30)];
redV.backgroundColor = [UIColor redColor];
[self addSubview:redV];
11邊緣碰撞行為.gif
  • 2.紅色view不添加到任何行為中</br>
11邊緣碰撞行為2.gif
  • 3.如果只想在碰到紅色view的時(shí)候方塊掉下去,紅色view不動杯缺,需要給碰撞檢測增減一條碰撞邊界
#pragma mark - 在紅色view的上方添加一個(gè)邊界到邊界檢測行為中
// 添加一個(gè)邊界,起點(diǎn)蒸播,終點(diǎn)作為一條直線。
CGPoint fromP = CGPointMake(0, 400);
CGPoint toP = CGPointMake(180, 400);
[collision addBoundaryWithIdentifier:@"line" fromPoint:fromP toPoint:toP];
11邊緣碰撞行為3.gif
  • 4.也可以增加一個(gè)路徑
注意:在增加路徑的時(shí)候碰撞會碰在弧線兩個(gè)頂點(diǎn)的連線的位置上萍肆,可以通過填充的模式看的更清楚一些袍榆。
// 添加路徑
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(200, 300) radius:100 startAngle:M_PI_2 endAngle:M_PI clockwise:YES];
[collision addBoundaryWithIdentifier:@"bcd" forPath:path];
11邊緣碰撞行為5.gif
  • 5.補(bǔ)充
    • 各種碰撞行為都有一個(gè)action的block,可以通過這個(gè)block監(jiān)聽在碰撞行為過程中的動態(tài)信息塘揣。

collision.action = ^(){
NSLog(@"%@", NSStringFromCGRect(self.box.frame));
};
* 可以設(shè)置邊緣檢測的代理包雀,根據(jù)identifer標(biāo)記去區(qū)分碰撞到哪一個(gè)邊界了。objc
// 設(shè)置代理
collision.collisionDelegate = self;

#pragma mark - UICollisionBehaviorDelegate
    - (void)collisionBehavior:(UICollisionBehavior*)behavior beganContactForItem:(id <UIDynamicItem>)item withBoundaryIdentifier:(nullable id <NSCopying>)identifier atPoint:(CGPoint)p {

    NSLog(@"%@", identifier);

}
```

多視圖附加行為

  • 1> 創(chuàng)建視圖
  • 2> 添加視圖之間的附著行為
  • 3> 添加重力行為
  • 4> 添加邊緣檢測行為
  • 5> 添加拖拽手勢亲铡,單獨(dú)處理頭部視圖的附著行為才写。
    • 在開始拖拽式葡兑,實(shí)例化拖拽行為。設(shè)置附著點(diǎn)為觸摸點(diǎn)
    • 在拖動過程中赞草,實(shí)時(shí)修改附著點(diǎn)為觸摸點(diǎn)
    • 在拖動結(jié)束后讹堤,移除附著行為,否則不能正常使用厨疙。
  • 效果圖</br>
    • 重力在下</br>
12多視圖附加行為.gif

* 重力在上</br>

12多視圖附加行為2.gif
  • 重力的方向可以通過
    @property (readwrite, nonatomic) CGVector gravityDirection;去設(shè)置

    • 根據(jù)x,y值的幾何方向去確定重力的方向洲守。
  • 界面效果:

    • 有9個(gè)圓形的球,一個(gè)比較大的作為頭部沾凄。
    • 在從屏幕任意位置拖動的時(shí)候所有的圓球都成一條串的效果梗醇,沿著重力方向下垂。
    • 抬起手指后撒蟀,自由墜落叙谨。
  • 1.創(chuàng)建9個(gè)view,并設(shè)置圓角半徑作為圓形牙肝,將最后一個(gè)修改為更大的效果唉俗。

    // 添加9個(gè)子控件
    CGFloat startX = 20;
    CGFloat startY = 100;
    CGFloat r = 10;
    
    NSMutableArray *arrM = [NSMutableArray arrayWithCapacity:9];
    for (int i = 0; i < 9; i++) {
    
        CGFloat x = startX + 2 * r * i;
        CGFloat y = startY;
        CGFloat width = 2 * r;
        CGFloat heigth = width;
    
        UIView *v = [[UIView alloc] initWithFrame:CGRectMake(x, y, width, heigth)];
    
        v.backgroundColor = [UIColor colorWithRed:arc4random_uniform(256)/255.0 green:arc4random_uniform(256)/255.0 blue:arc4random_uniform(256)/255.0 alpha:1.0];
    
        v.layer.cornerRadius = r;
    
        if (i == 8) {
            r = 20;
            v.backgroundColor = [UIColor greenColor];
            v.frame = CGRectMake(v.frame.origin.x, v.frame.origin.y - 10, 2 * r, 2 * r);
            v.layer.cornerRadius = r;
        }
    
        [self.view addSubview:v];
    
        // 保存到集合中
        [arrM addObject:v];
    
    }
    
  • 9個(gè)圓球效果圖</br>

13圓球.png
  • 遍歷集合中所有的元素并添加附加行為
    • 最后一個(gè)要留著,單獨(dú)處理
    // 添加吸附吸附行為
    for (int i = 0; i < 8; i++) {
        UIAttachmentBehavior *attachment = [[UIAttachmentBehavior alloc] initWithItem:arrM[i] attachedToItem:arrM[i+1]];
        [_animator addBehavior:attachment];
    }
    
  • 給所有的元素添加重力行為
// 重力仿真
UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:arrM];
// 指定重力的方向
gravity.gravityDirection = CGVectorMake(0.0, 1.0);
[_animator addBehavior:gravity];
  • 添加邊緣檢測行為
// 邊緣檢測
UICollisionBehavior *collision = [[UICollisionBehavior alloc] initWithItems:arrM];
collision.translatesReferenceBoundsIntoBoundary = YES;
[_animator addBehavior:collision];
  • 添加拖拽手勢,在拖拽手勢內(nèi)部根據(jù)狀態(tài)進(jìn)行處理
    • 在開始拖拽的時(shí)候讥蟆,給頭部的view實(shí)例化附著行為陕截,附著點(diǎn)就是觸摸點(diǎn)。
    • 在拖拽過程中坐漏,附加行為的附著點(diǎn)仍是觸摸點(diǎn)。
    • 在拖拽結(jié)束后,將附著行為從仿真者中移除敦姻。
    CGPoint loc = [pan locationInView:self.view];

    if (UIGestureRecognizerStateBegan == pan.state) {
        // 開始拖拽,實(shí)例化附加行為
        _attachment = [[UIAttachmentBehavior alloc] initWithItem:_headView attachedToAnchor:loc];
        [_animator addBehavior:_attachment];

    } else if (UIGestureRecognizerStateChanged == pan.state) {
        // 拖拽過程中
        _attachment.anchorPoint = loc;

    } else if (UIGestureRecognizerStateEnded == pan.state){
        // 結(jié)束拖拽
        [_animator removeBehavior:_attachment];

    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市歧杏,隨后出現(xiàn)的幾起案子镰惦,更是在濱河造成了極大的恐慌,老刑警劉巖犬绒,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件旺入,死亡現(xiàn)場離奇詭異,居然都是意外死亡凯力,警方通過查閱死者的電腦和手機(jī)茵瘾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來咐鹤,“玉大人拗秘,你說我怎么就攤上這事∑砘蹋” “怎么了雕旨?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵扮匠,是天一觀的道長。 經(jīng)常有香客問我凡涩,道長餐禁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任突照,我火速辦了婚禮帮非,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘讹蘑。我一直安慰自己末盔,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布座慰。 她就那樣靜靜地躺著陨舱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪版仔。 梳的紋絲不亂的頭發(fā)上游盲,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機(jī)與錄音蛮粮,去河邊找鬼益缎。 笑死,一個(gè)胖子當(dāng)著我的面吹牛然想,可吹牛的內(nèi)容都是我干的莺奔。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼变泄,長吁一口氣:“原來是場噩夢啊……” “哼令哟!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起妨蛹,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤屏富,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后蛙卤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體狠半,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年表窘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了典予。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡乐严,死狀恐怖瘤袖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情昂验,我是刑警寧澤捂敌,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布艾扮,位于F島的核電站,受9級特大地震影響占婉,放射性物質(zhì)發(fā)生泄漏泡嘴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一逆济、第九天 我趴在偏房一處隱蔽的房頂上張望酌予。 院中可真熱鬧,春花似錦奖慌、人聲如沸抛虫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽建椰。三九已至,卻和暖如春岛马,著一層夾襖步出監(jiān)牢的瞬間棉姐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工啦逆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留伞矩,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓蹦浦,卻偏偏與公主長得像扭吁,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子盲镶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容