源码0311-抽屉效果

 

抽屉效果:

观察(内容,事件):三个View,left,right,main;手势(Pan)滑动的时候(左划/右滑)改变View的位置/尺寸;

 

监听者模式:KVO 时刻监听对象的属性值的改变;

[_mainV addObserver:self forKeyPath:keyPath(_mainV, frame) options:NSKeyValueObservingOptionNew context:nil];

只要监听到keyPath属性有新的值,就是调用self的一个方法

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context这个是代理要实现的一个方法;

给谁添加了监听者,用完也要必须销毁这个观察者;

[_mainV removeObserver:self forKeyPath:@"frame"];

 

什么情况下需要用到自动提示宏???

#define keyPath(objc,keyPath) @(((void)objc.keyPath,#keyPath))

keyPath(objc,keyPath) 定义了名称和参数,传一个对象和属性;调用的时候直接传入对象和属性。

 参数:(void)objc.keyPath实现表达式有点语法提示,(void)没有返回值避免有警告;

#keyPath是将属性值转为字符串(C)返回出去即结果;(这里利用了一个逗号表达式)

@()即将C语言字符串转化为OC字符串;

结果:keyPath(_mainV,frame)==@“frame”;

 

//  ViewController.m
//  11-抽屉效果
// 宏里面的#,会自动把后面的参数变成C语言的字符串
#define keyPath(objc,keyPath) @(((void)objc.keyPath,#keyPath))


// 宏的操作原理,每输入一个字母就会直接把宏右边的拷贝,并且会自动补齐前面的内容。

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic, weak) UIView *mainV;
@property (nonatomic, weak) UIView *leftV;
@property (nonatomic, weak) UIView *rightV;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
//    #define keyPath(objc,keyPath) #(objc.keyPath,keyPath)
    // 逗号表达式,只取最右边的值  
    int a = ((void)5,2);
    
    
    // 如果把c语言字符串转OC字符串
    char *c = "abc";
    
    NSLog(@"%@", [@(c) class]);
    
    NSLog(@"%d",a);
    
//    NSLog(@"%s",keyPath(_mainV, frame));
    

    NSLog(@"%@",[keyPath(_mainV, frame) class]);
    
    // 添加子控件
    [self setUpChildView];
    
    // 添加Pan手势
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
    
    [self.view addGestureRecognizer:pan];
    
    
    // 利用KVO时刻监听mainV的frame属性
// Observer:观察者 谁想监听
    // KeyPath:监听的属性
    // options:监听新值的改变
    [_mainV addObserver:self forKeyPath:keyPath(_mainV, frame) options:NSKeyValueObservingOptionNew context:nil];
    
    // 什么情况下需要用到自动提示宏
    
    
}

// 只要监听的属性一改变,就会调用观察者的这个方法,通知你有新值
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    NSLog(@"%@",NSStringFromCGRect(_mainV.frame));
    if (_mainV.frame.origin.x > 0) { // 往右边移动,隐藏蓝色的view
        _rightV.hidden = YES;
    }else if (_mainV.frame.origin.x < 0){ // 往左边移动,显示蓝色的view
        _rightV.hidden = NO;
        
    }
}
//和通知好像
// 在对象销毁的时候,一定要注意移除观察者
- (void)dealloc
{
    // 移除观察者
    [_mainV removeObserver:self forKeyPath:@"frame"];
}



#pragma mark - pan的方法
- (void)pan:(UIPanGestureRecognizer *)pan
{
    // 获取手势的移动的位置
    CGPoint transP = [pan translationInView:self.view];
    
    // 获取X轴的偏移量
    CGFloat offsetX = transP.x;
    
    // 修改mainV的Frame
    _mainV.frame = [self frameWithOffsetX:offsetX];
    
    // 复位(每次仅得到当次的偏移量值)偏移量归零
    [pan setTranslation:CGPointZero inView:self.view];
}

#pragma mark - 根据offsetX计算mainV的Frame
- (CGRect)frameWithOffsetX:(CGFloat)offsetX
{
    CGRect frame = _mainV.frame;
    frame.origin.x += offsetX;
    
    return frame;
}

#pragma mark - 添加子控件
- (void)setUpChildView
{
    // left
    UIView *leftV = [[UIView alloc] initWithFrame:self.view.bounds];
    
    leftV.backgroundColor = [UIColor greenColor];
    
    [self.view addSubview:leftV];
    
    _leftV = leftV;
    
    // right
    UIView *rightV = [[UIView alloc] initWithFrame:self.view.bounds];
    
    rightV.backgroundColor = [UIColor blueColor];
    
    [self.view addSubview:rightV];
    
    _rightV = rightV;
    
    // main
    UIView *mainV = [[UIView alloc] initWithFrame:self.view.bounds];
    
    mainV.backgroundColor = [UIColor redColor];
    
    [self.view addSubview:mainV];
    
    _mainV = mainV;
}

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

@end

 

01-抽屉效果(复杂实现)

KVO时刻都在监听,比较耗性能,所以能少用就少用;

 

等比缩放:

offsetX 已知,假设KMaxY移动的距离为80;

offsetY =offsetX *kMaxY / screenW;

preW = frame.size.height

preH = frame.size.width

 

curH =preH - 2*offsetY;

 

scale = curH /preH;

curW = preW *scale;

 

frame.origin.x +=offsetX;

y = (screenH - cur)/2;

frame.origin.y = y;

frame.size.height = curH;

frame.size.width = curW;

 

offsetX 往右滑其值是正数;offsetY也为正数;preH - 2*offsetY 值会越来越小;

offsetX 往左滑其值为负数;offsetY也为负数;preH - 2*offsetY 值会越来越大;

//  ViewController.m
//  11-抽屉效果

// 宏里面的#,会自动把后面的参数变成C语言的字符串
#define keyPath(objc,keyPath) @(((void)objc.keyPath,#keyPath))


// 宏的操作原理,每输入一个字母就会直接把宏右边的拷贝,并且会自动补齐前面的内容。

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic, weak) UIView *mainV;
@property (nonatomic, weak) UIView *leftV;
@property (nonatomic, weak) UIView *rightV;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    // 添加子控件
    [self setUpChildView];
    
    // 添加Pan手势
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
    
    [self.view addGestureRecognizer:pan];
    
}

// 只要监听的属性一改变,就会调用观察者的这个方法,通知你有新值
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    NSLog(@"%@",NSStringFromCGRect(_mainV.frame));
    
    if (_mainV.frame.origin.x > 0) { // 往右边移动,隐藏蓝色的view
        _rightV.hidden = YES;
        
    }else if (_mainV.frame.origin.x < 0){ // 往左边移动,显示蓝色的view
        _rightV.hidden = NO;
        
    }
}




#pragma mark - pan的方法
- (void)pan:(UIPanGestureRecognizer *)pan
{
    // 获取手势的移动的位置
    CGPoint transP = [pan translationInView:self.view];
    
    // 获取X轴的偏移量
    CGFloat offsetX = transP.x;
    
    // 修改mainV的Frame
    _mainV.frame = [self frameWithOffsetX:offsetX];
    
    // 判断下mainV的x是否大于0
    [self observeValueForKeyPath:nil ofObject:nil change:nil context:nil];
    
    // 复位
    [pan setTranslation:CGPointZero inView:self.view];
}

// 手指往右移动,视图X轴也要往右移动(x++),y轴往下移动(y增加),尺寸缩放(按比例)。
#define kMaxY 80
#pragma mark - 根据offsetX计算mainV的Frame
- (CGRect)frameWithOffsetX:(CGFloat)offsetX
{
    // 获取上一次的frame
    CGRect frame = _mainV.frame;
    // 获取屏幕的高度
    CGFloat screenH = [UIScreen mainScreen].bounds.size.height;
    CGFloat screenW = [UIScreen mainScreen].bounds.size.width;
    
    // X轴每平移一点,Y轴需要移动
    CGFloat offsetY = offsetX * kMaxY / screenW;
    
    // 获取上一次的高度
    CGFloat preH = frame.size.height;
    
    // 获取上一次的宽度
    CGFloat preW = frame.size.width;
    
    // 获取当前的高度
    CGFloat curH = preH - 2 * offsetY;
    if (frame.origin.x < 0) { // 往左移动
        curH = preH + 2 * offsetY;
    }
    
    // 获取尺寸的缩放比例
    CGFloat scale = curH / preH;
    
    // 获取当前的宽度
    CGFloat curW = preW * scale;
    
    // 更改frame
    
    // 获取当前X
    frame.origin.x += offsetX;
    
    // 获取当前Y
    CGFloat y = (screenH - curH) / 2;
    
    frame.origin.y = y;
    
    frame.size.height = curH;
    
    frame.size.width = curW;
    
    return frame;
}

#pragma mark - 添加子控件
- (void)setUpChildView
{
    // left
    UIView *leftV = [[UIView alloc] initWithFrame:self.view.bounds];
    
    leftV.backgroundColor = [UIColor greenColor];
    
    [self.view addSubview:leftV];
    
    _leftV = leftV;
    
    // right
    UIView *rightV = [[UIView alloc] initWithFrame:self.view.bounds];
    
    rightV.backgroundColor = [UIColor blueColor];
    
    [self.view addSubview:rightV];
    
    _rightV = rightV;
    
    // main
    UIView *mainV = [[UIView alloc] initWithFrame:self.view.bounds];
    
    mainV.backgroundColor = [UIColor redColor];
    
    [self.view addSubview:mainV];
    
    _mainV = mainV;
}

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

@end

 

02-抽屉效果(定位+复位)

//  ViewController.h
//  11-抽屉效果
#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

// 设计原理:如果需要把控件暴露出去,一定要要写readonly
@property (nonatomic, weak, readonly) UIView *mainV;
@property (nonatomic, weak, readonly) UIView *leftV;
@property (nonatomic, weak, readonly) UIView *rightV;

@end
//  ViewController.m
//  11-抽屉效果
// 宏里面的#,会自动把后面的参数变成C语言的字符串
#define keyPath(objc,keyPath) @(((void)objc.keyPath,#keyPath))


// 获取屏幕的宽度
#define screenW  [UIScreen mainScreen].bounds.size.width

// 宏的操作原理,每输入一个字母就会直接把宏右边的拷贝,并且会自动补齐前面的内容。

#import "ViewController.h"

@interface ViewController ()



@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    // 添加子控件
    [self setUpChildView];
    
    // 添加Pan手势
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
    
    [self.view addGestureRecognizer:pan];
    
//    // 添加点按手势
//    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap)];
//    
//    [self.view addGestureRecognizer:tap];
    
    
}

#pragma mark - 点按手势
//- (void)tap
//{
//    // 还原
//    if (_mainV.frame.origin.x != 0) {
//        [UIView animateWithDuration:0.25 animations:^{
//            
//            _mainV.frame = self.view.bounds;
//        }];
//    }
//}

// 只要监听的属性一改变,就会调用观察者的这个方法,通知你有新值
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    NSLog(@"%@",NSStringFromCGRect(_mainV.frame));
    
    if (_mainV.frame.origin.x > 0) { // 往右边移动,隐藏蓝色的view
        _rightV.hidden = YES;
        
    }else if (_mainV.frame.origin.x < 0){ // 往左边移动,显示蓝色的view
        _rightV.hidden = NO;
        
    }
}



#define kTargetR 275

#define kTargetL -250


#pragma mark - pan的方法
- (void)pan:(UIPanGestureRecognizer *)pan
{
    // 获取手势的移动的位置
    CGPoint transP = [pan translationInView:self.view];
    
    // 获取X轴的偏移量
    CGFloat offsetX = transP.x;
    
    // 修改mainV的Frame
    _mainV.frame = [self frameWithOffsetX:offsetX];
    
    // 判断下mainV的x是否大于0
    [self observeValueForKeyPath:nil ofObject:nil change:nil context:nil];
    
    // 复位
    [pan setTranslation:CGPointZero inView:self.view];
    
    // 判断下当手势结束的时候,定位
    if (pan.state == UIGestureRecognizerStateEnded) {
        // 定位
        
        
        CGFloat target = 0;
        // 1.判断下main.x > screenW * 0.5,定位到右边 x=275
        if (_mainV.frame.origin.x > screenW * 0.5) {
            // 定位到右边
            target = kTargetR;
        }else if (CGRectGetMaxX(_mainV.frame) < screenW * 0.5){
            // 2.判断下max(main.x) < screenW * 0.5
            target = kTargetL;
        }
        
        // 获取x轴偏移量
        CGFloat offsetX = target - _mainV.frame.origin.x;
        
       
        [UIView animateWithDuration:0.25 animations:^{
            
            _mainV.frame = target == 0?self.view.bounds:[self frameWithOffsetX:offsetX];
        }];
        
        
    }
    
}

// 手指往右移动,视图X轴也要往右移动(x++),y轴往下移动(y增加),尺寸缩放(按比例)。
#define kMaxY 80
#pragma mark - 根据offsetX计算mainV的Frame
- (CGRect)frameWithOffsetX:(CGFloat)offsetX
{
    // 获取上一次的frame
    CGRect frame = _mainV.frame;
    // 获取屏幕的高度
    CGFloat screenH = [UIScreen mainScreen].bounds.size.height;
//    // 获取屏幕的宽度
//    CGFloat screenW = [UIScreen mainScreen].bounds.size.width;
    
    // X轴每平移一点,Y轴需要移动
    CGFloat offsetY = offsetX * kMaxY / screenW;
    
    // 获取上一次的高度
    CGFloat preH = frame.size.height;
    
    // 获取上一次的宽度
    CGFloat preW = frame.size.width;
    
    // 获取当前的高度
    CGFloat curH = preH - 2 * offsetY;
    if (frame.origin.x < 0) { // 往左移动
        curH = preH + 2 * offsetY;
    }
    
    // 获取尺寸的缩放比例
    CGFloat scale = curH / preH;
    
    // 获取当前的宽度
    CGFloat curW = preW * scale;
    
    // 更改frame
    
    // 获取当前X
    frame.origin.x += offsetX;
    
    // 获取当前Y
    CGFloat y = (screenH - curH) / 2;
    
    frame.origin.y = y;
    
    frame.size.height = curH;
    
    frame.size.width = curW;
    
    return frame;
}

#pragma mark - 添加子控件
- (void)setUpChildView
{
    // left
    UIView *leftV = [[UIView alloc] initWithFrame:self.view.bounds];
    
    leftV.backgroundColor = [UIColor greenColor];
    
    [self.view addSubview:leftV];
    
    _leftV = leftV;
    
    // right
    UIView *rightV = [[UIView alloc] initWithFrame:self.view.bounds];
    
    rightV.backgroundColor = [UIColor blueColor];
    
    [self.view addSubview:rightV];
    
    _rightV = rightV;
    
    // main
    UIView *mainV = [[UIView alloc] initWithFrame:self.view.bounds];
    
    mainV.backgroundColor = [UIColor redColor];
    
    [self.view addSubview:mainV];
    
    _mainV = mainV;
}

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

@end

 

//  XMGMainViewController.h
//  11-抽屉效果
#import "ViewController.h"

@interface XMGMainViewController : ViewController

@end
//  XMGMainViewController.m
//  11-抽屉效果
#import "XMGMainViewController.h"
#import "XMGViewController.h"

@interface XMGMainViewController ()

@end

@implementation XMGMainViewController
// 设计原理,如果A控制器的view成为B控制器View是子控件,注意A控制器一定要成为B控制器的子控制器
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    // 创建tableView控制器(A)
    XMGViewController *vc = [[XMGViewController alloc] init];
    vc.view.frame = self.view.bounds;
    
    // A成为B控制器的子控制器
    [self addChildViewController:vc];
    
    // 主视图展示tableView
    [self.mainV addSubview:vc.view];
    
}

- (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

 

//  XMGViewController.h
//  11-抽屉效果
#import <UIKit/UIKit.h>

@interface XMGViewController : UITableViewController

@end
//  XMGViewController.m
//  11-抽屉效果
#import "XMGViewController.h"

@interface XMGViewController ()

@end

@implementation XMGViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // Uncomment the following line to preserve selection between presentations.
    // self.clearsSelectionOnViewWillAppear = NO;
    
    // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
    // self.navigationItem.rightBarButtonItem = self.editButtonItem;
}

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

#pragma mark - Table view data source


- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
#warning Incomplete method implementation.
    // Return the number of rows in the section.
    return 30;
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    static NSString *ID = @"cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID];
    }
    
    
    cell.textLabel.text = [NSString stringWithFormat:@"%ld",indexPath.row];
    // Configure the cell...
    
    return cell;
}

@end

 

posted @ 2017-04-05 16:39  laugh  阅读(212)  评论(0编辑  收藏  举报