傲视苍穹iOS《Objective-C》VIP专题iOS猛码计划iOS日常

iOS中UIDynamic物理仿真详解

2016-02-20  本文已影响4226人  si1ence

本文中所有代码演示均有GitHub源码,点击下载

UIDynamic简介

UIDynamic中的三个重要概念

项目搭建演练

02重力有边界.gif 03旋转.gif

项目框架搭建

一、结构分析

二、代码实现

1> 列表控制器

}
```

05行为列表.png
2> 跳转到演示控制器
    #pragma mark - 代理方法
    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

    // 1. 实例化一个仿真管理器
    WPFDemoController *demoVc = [[WPFDemoController alloc] init];

    // 2. 设置标题
    demoVc.title = _dynamicArr[indexPath.row];

    // 3. 传递功能类型
    demoVc.function = (int)indexPath.row;

    // 4. 跳转界面
    [self.navigationController pushViewController:demoVc animated:YES];
    }
3> 演示控制器根据索引去加载不同的view
// 重写其initWithFrame 方法,设置基本信息
- (instancetype)initWithFrame:(CGRect)frame {

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

        // 设置方块
        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;
}
// 在此之前要先在头文件中定义枚举类型
- (void)viewDidLoad {
    [super viewDidLoad];

    // 新建一个空的baseView
    WPFBaseView *baseView = nil;

    // 根据不同的功能类型选择不同的视图
    // 运用了多态
    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];

}

06根据不同的行为去加载view.gif

吸附行为:WPFSnapView

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

    // 0. 触摸之前要清零之前的吸附事件,否则动作越来越小
    [self.animator removeAllBehaviors];

    // 1. 获取触摸对象
    UITouch *touch = [touches anyObject];

    // 2. 获取触摸点
    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];
}
07吸附行为.gif

推动行为:WPFPushView

@interface WPFPushView ()
{
    UIImageView *_smallView;    // 显示在第一个触摸点位置的图片框
    UIPushBehavior *_push;      // 推动的行为
    CGPoint _firstPoint;        // 手指点击的第一个点
    CGPoint _currentPoint;      // 当前触摸点
}
@end
// 重写init 方法
- (instancetype)init {

    if (self = [super init]) {

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



        // 2. 添加图片框,拖拽起点
        UIImageView *smallView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"AttachmentPoint_Mask"]];
        // 该图片框默认是隐藏的,在触摸屏幕的时候再显示出来
        smallView.hidden = YES;
        [self addSubview:smallView];
        // 建立全局关系
        _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;
}
    // 监听开始拖拽的方法
- (void)panAction:(UIPanGestureRecognizer *)pan {

    // 如果是刚开始拖拽,则设置起点处的小圆球
    if (pan.state == UIGestureRecognizerStateBegan) {

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


    // 如果当前拖拽行为正在移动
    } else if (pan.state == UIGestureRecognizerStateChanged) {

        _currentPoint = [pan locationInView:self];

        // 重绘当前页面
        [self setNeedsDisplay];

    // 如果当前拖拽行为结束
    } else if (pan.state == UIGestureRecognizerStateEnded){

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

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

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

        // 3. 计算距离
        CGFloat distance = hypot(offset.y, offset.x);

        // 4. 设置推动的力度,与线的长度成正比
        _push.magnitude = directtion / 10;


        // 5. 使单次推行为有效
        _push.active = YES;

        // 6. 将拖拽的线隐藏
        _firstPoint = CGPointZero;
        _currentPoint = CGPointZero;

        // 7. 将起点的小圆隐藏
        _smallView.hidden = YES;

        // 8. 进行重绘
        [self setNeedsDisplay];
    }
}
- (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. 设置线宽
    path.lineWidth = 7;

    // 5. 线的颜色
    [[UIColor greenColor] setStroke];

    // 6. 渲染
    [path stroke];

}
08推行为.gif

刚性附着行为:WPFAttachmentView

注意: 只要设置了以下两个属性,即为弹性连接
@interface WPFPushView ()
{
    // 附着点图片框
    UIImageView *_anchorImgView;

    // 参考点图片框(boxView 内部)
    UIImageView *_offsetImgView;
}
@end
- (instancetype)init {
    if (self = [super init]) {

        // 1. 设置boxView 的中心点
        self.boxView.center = CGPointMake(200, 200);

        // 2. 添加附着点
        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. 设置附着点图片(即直杆与被拖拽图片的连接点)
        UIImage *image = [UIImage imageNamed:@"AttachmentPoint_Mask"];
        UIImageView *anchorImgView = [[UIImageView alloc] initWithImage:image];
        anchorImgView.center = anchorPoint;

        [self addSubview:anchorImgView];
        _anchorImgView = anchorImgView;

        // 3. 设置参考点
        _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;
}
// 拖拽的时候会调用的方法
- (void)panAction:(UIPanGestureRecognizer *)pan {

    // 1. 获取触摸点
    CGPoint loc = [pan locationInView:self];

    // 2. 修改附着行为的附着点
    _anchorImgView.center = loc;
    self.attachment.anchorPoint = loc;

    // 3. 进行重绘
    [self setNeedsDisplay];

}
- (void)drawRect:(CGRect)rect {
    // 1.获取图形上下文
    CGContextRef context = UIGraphicsGetCurrentContext();

    // 2.设置路径起点
    CGContextMoveToPoint(context, _anchorImage.center.x, _anchorImage.center.y);

    // 2.2设置路径画线的点,注意需要将轴点的坐标进行转换
    // 使得两个点的坐标位于同一个坐标系下
    // addline
    // 去偏移点相对于父视图的坐标
    CGPoint p = [self convertPoint:_offsetImage.center     fromView:self.box];
    CGContextAddLineToPoint(context, p.x, p.y);

    // 2.3设置虚线样式
    CGFloat lengths[] = {10.0f, 8.0f};
    CGContextSetLineDash(context, 0.0, lengths, 2);

    // 2.4设置线宽
    CGContextSetLineWidth(context, 5.0f);

    // 3.渲染,绘制路径
    CGContextDrawPath(context, kCGPathStroke);
}
09刚性附着行为.gif

* 中心点偏移</br>

09刚性附着行为2.gif

弹性附着行为:WPFSpringView

// 振幅
self.attachment.damping = 0.1f;
// 频率
self.attachment.frequency = 1.0f;
// WPFAttachView是刚性附着行为的view,WPFSpringView为弹性附着行为的view
@interface WPFSpringView : WPFAttachView
@property (nonatomic, weak) UIAttachmentBehavior *attachment;
// 振幅
self.attachment.damping = 0.1f;
// 频率
self.attachment.frequency = 1.0f;
10弹性附着行为.gif
// KVO监听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
// 添加重力
UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:@[self.box]];
[self.animator addBehavior:gravity];
10弹性附着行为3.gif 10弹性附着行为4.gif
// 添加碰撞检测
UICollisionBehavior *collision = [[UICollisionBehavior alloc] initWithItems:@[self.box]];
collision.translatesReferenceBoundsIntoBoundary = YES;
[self.animator addBehavior:collision];
10弹性附着行为5.gif

碰撞检测

// 增加一个红色的条状view
UIView *redV = [[UIView alloc] initWithFrame:CGRectMake(0, 400, 180, 30)];
redV.backgroundColor = [UIColor redColor];
[self addSubview:redV];
11边缘碰撞行为.gif 11边缘碰撞行为2.gif
#pragma mark - 在红色view的上方添加一个边界到边界检测行为中
// 添加一个边界,起点,终点作为一条直线。
CGPoint fromP = CGPointMake(0, 400);
CGPoint toP = CGPointMake(180, 400);
[collision addBoundaryWithIdentifier:@"line" fromPoint:fromP toPoint:toP];
11边缘碰撞行为3.gif
注意:在增加路径的时候碰撞会碰在弧线两个顶点的连线的位置上,可以通过填充的模式看的更清楚一些。
// 添加路径
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

collision.action = ^(){
NSLog(@"%@", NSStringFromCGRect(self.box.frame));
};
* 可以设置边缘检测的代理,根据identifer标记去区分碰撞到哪一个边界了。objc
// 设置代理
collision.collisionDelegate = self;

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

    NSLog(@"%@", identifier);

}
```

多视图附加行为

12多视图附加行为.gif

* 重力在上</br>

12多视图附加行为2.gif 13圆球.png
// 重力仿真
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];
    CGPoint loc = [pan locationInView:self.view];

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

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

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

    }
上一篇下一篇

猜你喜欢

热点阅读