源码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