• 源码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
    本人无商业用途,仅仅是学习做个笔记,特别鸣谢小马哥,学习了IOS,另日语学习内容有需要文本和音频请关注公众号:riyuxuexishuji
  • 相关阅读:
    window.location.href的用法
    echarts折线图阴影设置
    SVN使用教程图文教程
    jksj算法训练营-第二课02 时间复杂度和空间复杂度分析
    jkjj算法训练营笔记-第二课01 训练环境配置、编码技巧和code style
    MySQL 基础模块的面试题总结
    MySQL 事务的面试题总结
    MySQL 中锁的面试题总结
    MySQL 命令和内置函数
    MySQL 性能优化 & 分布式
  • 原文地址:https://www.cnblogs.com/laugh/p/6669395.html
Copyright © 2020-2023  润新知