ios物理仿真

1、父类

#import 

@interface WPFBaseView : UIView

/**  方块视图  */
@property (nonatomic, weak) UIImageView *boxView;

/**  仿真者  */
@property (nonatomic, strong) UIDynamicAnimator *animator;

@end
#import "WPFBaseView.h"

@implementation WPFBaseView

- (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;
    
}
  • 吸附行为
#import "WPFBaseView.h"

@interface WPFSnapView : WPFBaseView

@end

#import "WPFSnapView.h"

@implementation WPFSnapView

- (void)touchesBegan:(NSSet *)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];
    
}

@end
  • 推动行为
#import "WPFBaseView.h"

@interface WPFPushView : WPFBaseView

@end
#import "WPFPushView.h"

@interface WPFPushView ()
{
    UIImageView *_smallView;
    UIPushBehavior *_push;
    CGPoint _firstPoint;
    CGPoint _currentPoint;
}

@end

@implementation WPFPushView

// 重写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 = atan2(offset.y, offset.x);
        
        // 3. 计算距离
        CGFloat distance = hypot(offset.y, offset.x);
        
        // 4. 设置推动的大小、角度
        _push.magnitude = distance;
        _push.angle = angle;
        
        // 5. 使单次推行为有效
        _push.active = YES;
        
        // 6. 将拖拽的线隐藏
        _firstPoint = CGPointZero;
        _currentPoint = CGPointZero;
        
        // 2. 将起点的小圆隐藏
        _smallView.hidden = YES;
        [self setNeedsDisplay];
        
        
    }
    
    
}

- (void)drawRect:(CGRect)rect {
    
    // 1. 开启上下文对象
    CGContextRef ctxRef = UIGraphicsGetCurrentContext();
    
    // 2 创建路径对象
    
    CGContextMoveToPoint(ctxRef, _firstPoint.x, _firstPoint.y);
    CGContextAddLineToPoint(ctxRef, _currentPoint.x, _currentPoint.y);
    
    // 3. 设置线宽和颜色
    CGContextSetLineWidth(ctxRef, 10);
    CGContextSetLineJoin(ctxRef, kCGLineJoinRound);
    CGContextSetLineCap(ctxRef, kCGLineCapRound);
    [[UIColor colorWithRed:arc4random_uniform(256) / 255.0 green:arc4random_uniform(256) / 255.0 blue:arc4random_uniform(256) / 255.0 alpha:1.0] setStroke];
    
    // 4. 渲染
    CGContextStrokePath(ctxRef);
}


@end
  • 刚性附着行为
#import "WPFBaseView.h"

@interface WPFAttachmentView : WPFBaseView

@property (nonatomic, strong) UIAttachmentBehavior *attachment;

@end
#import "WPFAttachmentView.h"

@interface WPFAttachmentView ()
{
    // 附着点图片框
    UIImageView *_anchorImgView;
    
    // 参考点图片框(boxView 内部)
    UIImageView *_offsetImgView;
}
@end

@implementation WPFAttachmentView

- (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. 获取路径
    UIBezierPath *bezierPath = [UIBezierPath bezierPath];
    
    // 2. 划线
    [bezierPath moveToPoint:_anchorImgView.center];
    
    CGPoint p = [self convertPoint:_offsetImgView.center fromView:self.boxView];
    [bezierPath addLineToPoint:p];
    
    bezierPath.lineWidth = 6;
    
    // 3. 渲染
    [bezierPath stroke];
    
}


@end
  • 弹性附着行为
#import "WPFBaseView.h"
#import "WPFAttachmentView.h"

@interface WPFSpringView : WPFAttachmentView

@end

#import "WPFSpringView.h"

@implementation WPFSpringView

/*
 
 * KVO键值监听(观察者)->观察者模式
 * 如果没有观察者,需要我们自己定时去查看状态,对应的是【轮询】,观察者是替代我们解决轮询的问题。
 * 观察者模式的性能并不好,在实际开发中,要慎用!
 * 用完观察者最后要释放观察者
 
 * KVO参数说明:
 * 1> 观察者,谁来负责"对象"的"键值"变化的观察
 * 2> 观察的键值
 * 3> 数值发生变化时,通知哪一个数值变化。
 * 4> 通常是nil,要传也可以传一个字符串
 
 * 监听方法说明:
 * 1> 观察的键值
 * 2> 观察的对象
 * 3> 数值的新旧内容,取决于定义观察者时的选项
 * 4> 定义观察者时设置的上下文
 
 */

- (instancetype)init {
    
    if (self = [super init]) {
        
        // 振幅
        //self.attachment.damping = 1.0f;
        
        // 频率(让线具有弹性)
        self.attachment.frequency = 1.0f;
        
        UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:@[self.boxView]];
        [self.animator addBehavior:gravity];
        
        // 利用KVO监听方块中心点的改变
        /**
         self.boxView   被监听的对象
         observer       监听者
         keypath        监听的键值
         options        监听什么值
         */
        
        [self.boxView addObserver:self forKeyPath:@"center" options:NSKeyValueObservingOptionNew context:nil];
        
    }
    
    return self;
    
}

// 监听,当boxView 的中心店改变时就进行冲重绘
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    
    [self setNeedsDisplay];
}


#warning 销毁的时候要移除监听
- (void)dealloc {
    
    [self.boxView removeObserver:self forKeyPath:@"center"];
    
}

/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
    // Drawing code
}
*/

@end
  • 碰撞检测
#import "WPFBaseView.h"

@interface WPFCollisionView : WPFBaseView

@end

#import "WPFCollisionView.h"

@interface WPFCollisionView () 

@end

@implementation WPFCollisionView

- (instancetype)init {
    
    if (self = [super init]) {
        
        
        self.boxView.center = CGPointMake(190, 0);
        
        // 1. 添加重力行为
        UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:@[self.boxView]];
        
        [self.animator addBehavior:gravity];
        
        // 2. 边缘检测
        // 如果把红色view 也加边缘检测,则碰撞后红色View 也会被碰掉,因此要手动添加边界
        UICollisionBehavior *collision = [[UICollisionBehavior alloc] initWithItems:@[self.boxView]];
        // 让碰撞的行为生效
        collision.translatesReferenceBoundsIntoBoundary = YES;
        
        collision.collisionDelegate = self;
        
        
        // 3. 添加一个红色view
        UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(0, 350, 180, 30)];
        redView.backgroundColor = [UIColor redColor];
        [self addSubview:redView];
        
        
        // 4. 手动添加边界
        UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRect:redView.frame];
        
        [collision addBoundaryWithIdentifier:@"redBoundary" forPath:bezierPath];
        
        [self.animator addBehavior:collision];
        
        // 5. 物体的属性行为
        UIDynamicItemBehavior *item = [[UIDynamicItemBehavior alloc] initWithItems:@[self.boxView]];
        
        // 设置物体弹性,振幅
        item.elasticity = 0.8;
        [self.animator addBehavior:item];
    }
    return self;
}


#pragma mark - UICollisionBehaviorDelegate
// 在碰撞的时候调用
- (void)collisionBehavior:(UICollisionBehavior *)behavior beganContactForItem:(id)item withBoundaryIdentifier:(id)identifier atPoint:(CGPoint)p {
    
    NSLog(@"%@", NSStringFromCGPoint(p));
    
    //    UIView *view = (UIView *)item;
    //    view.backgroundColor = [UIColor colorWithRed:arc4random_uniform(256)/255.0 green:arc4random_uniform(256)/255.0 blue:arc4random_uniform(256)/255.0 alpha:1.0];
    
}

@end

2、视图控制器类

#import 

typedef enum {
    
    kDemoFunctionSnap = 0,
    kDemoFunctionPush,
    kDemoFunctionAttachment,
    kDemoFunctionSpring,
    kDemoFunctionCollision
    
} kDemoFunction;

@interface WPFDemoController : UIViewController

/** 功能类型 */
@property (nonatomic, assign) kDemoFunction function;
@end
#import "WPFDemoController.h"
#import "WPFSnapView.h"
#import "WPFPushView.h"
#import "WPFAttachmentView.h"
#import "WPFSpringView.h"
#import "WPFCollisionView.h"

@interface WPFDemoController ()

@end

@implementation WPFDemoController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    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];
    
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

@end

3、调用

#import "ViewController.h"
#import "WPFDemoController.h"

@interface ViewController ()
{
    NSArray *_dynamicArr;
}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    
    _dynamicArr = @[@"吸附行为", @"推动行为", @"刚性附着行为", @"弹性附着行为", @"碰撞检测"];
    
    self.tableView.tableFooterView = [[UIView alloc] init];
    

    
}

// 几组
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

// 几行
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return 5;
}

// 每行的具体内容
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    // 1. 设置可重用id
    NSString *identifier = @"helloCell";
    
    // 2. 根据可重用id 去tableView 的缓存区去找
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
    
    // 3. 如果找不到,就重新实例化一个
    if (nil == cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    }
    
    cell.textLabel.text = _dynamicArr[indexPath.row];
    
    return cell;
}

// 执行代理方法
- (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];

}



@end

demo地址https://github.com/belink-QD/Dynamic

你可能感兴趣的:(ios物理仿真)