ios物理仿真

1、父类

#import <UIKit/UIKit.h>

@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<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];
    
}

@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<NSString *,id> *)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 () <UICollisionBehaviorDelegate>

@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<UIDynamicItem>)item withBoundaryIdentifier:(id<NSCopying>)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 <UIKit/UIKit.h>

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容