美文网首页
iOS 无规律总结[UI进阶篇]

iOS 无规律总结[UI进阶篇]

作者: HuangLinWang | 来源:发表于2017-01-19 20:56 被阅读0次

    iOS 无规律总结[UI基础篇]http://www.jianshu.com/p/93eef73bf235

    图文混排富文本

    例子:使用图像和文本生成上下排列的属性文本
    /// @param image      图像
    /// @param imageWH    图像宽高
    /// @param title      标题文字
    /// @param fontSize   标题字体大小
    /// @param titleColor 标题颜色
    /// @param spacing    图像和标题间距
    + (instancetype)imageTextWithImage:(UIImage *)image imageWH:(CGFloat)imageWH title:(NSString *)title fontSize:(CGFloat)fontSize titleColor:(UIColor *)titleColor spacing:(CGFloat)spacing {
        
        // 文本字典
        NSDictionary *titleDict = @{NSFontAttributeName: [UIFont systemFontOfSize:fontSize],
                                    NSForegroundColorAttributeName: titleColor};
        NSDictionary *spacingDict = @{NSFontAttributeName: [UIFont systemFontOfSize:spacing]};
        
        // 图片文本
        NSTextAttachment *attachment = [[NSTextAttachment alloc] init];
        attachment.image = image;
        attachment.bounds = CGRectMake(0, 0, imageWH, imageWH);
        NSAttributedString *imageText = [NSAttributedString attributedStringWithAttachment:attachment];
        
        // 换行文本
        NSAttributedString *lineText = [[NSAttributedString alloc] initWithString:@"\n\n" attributes:spacingDict];
        
        // 按钮文字
        NSAttributedString *text = [[NSAttributedString alloc] initWithString:title attributes:titleDict];
        
        // 合并文字
        NSMutableAttributedString *attM = [[NSMutableAttributedString alloc] initWithAttributedString:imageText];
        [attM appendAttributedString:lineText];
        [attM appendAttributedString:text];
        
        return attM.copy;
    }
    

    毛玻璃效果

    UIBlurEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
    //毛玻璃效果
    UIVisualEffectView *blurView = [[UIVisualEffectView alloc]initWithEffect:effect];
    // 设置frame
    // 添加到图片上
    

    UITextField

    //设置textfield的边框
    textField.borderStyle = UITextBorderStyleRoundedRect;
    可选属性:
          UITextBorderStyleNone,无边框
          UITextBorderStyleLine,有边框
          UITextBorderStyleBezel,有边框和阴影
          UITextBorderStyleRoundedRect圆角
    密文输入      
         textField.secureTextEntry = YES;
    提示文字  
        textField.placeholder = @"请输入";   
    再次编辑时是否清空之前内容;默认NO
         textField.clearsOnBeginEditing = YES      
    对齐方式
        textField.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter 
    
    可选属性:
    UIControlContentVerticalAlignmentCenter  居中对齐
    UIControlContentVerticalAlignmentTop    顶部对齐,默认是顶部对齐
    UIControlContentVerticalAlignmentBottom 底部对齐
    UIControlContentVerticalAlignmentFill    完全填充  
    
    设置return键
        textField.returnKeyType = UIReturnKeyDefault
    
    点击键盘上Return按钮时候调用
    - (BOOL)textFieldShouldReturn:(UITextField *)textField
    

    断言

    condition 条件
    desc 错误时的描述! -> 写字符串!
    NSAssert(condition, desc, ...)
    
    

    UISegmentedControl

    // 初始化
    UISegmentedControl *segment = [[UISegmentedControl alloc] initWithItems:@[@"分组", @"全部"]];
    
    // 设置宽度
    [segment setWidth:宽度 forSegmentAtIndex:下标];
    
    // 设置选中
    [segment setSelectedSegmentIndex:0];
    
    // 选中的下标.可以再通知中使用
    sender.selectedSegmentIndex
    

    心得体会<欢迎大神指导>

    在搭建基本的框架的时候,可以创建一个Base控制器,里面写一些公用的方法和属性.
    也可以创建一个基于Base控制器的事件Function控制器,用来写一些公用的方法.
    不一定要让每一个控制器都用到.但是可以减少一些代码的书写.
    

    LoadView

    注意: 
    1. 在loadView方法中最好不要调用self.view,如果视图没有被创建和设置,会造成死循环!
    2. 如果控制器的视图是在storyboard或xib文件中创建的,不要重写loadView方法!
    3. 在loadView方法中不要调用super
    4. 在loadView方法中慎用self.view
    5. super的调用,如果放到最下面!我们自己写的都白写了!
    例子:
    - (void)loadView {    
        [super loadView]; // self.view = [[UIView alloc]init];    
        self.view = [[UITableView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];    
        UIButton *btn = [UIButton buttonWithType:UIButtonTypeContactAdd];
        [self.view addSubview:btn];    
    }
    官方解释:
    You should never call this method directly.
     // 禁止直接调用此方法
     The view controller calls this method when its view property is requested but is currently nil.
     // 如果用到了view属性,当时view时nil的时候,系统会自动调用此方法!
     
     This method loads or creates a view and assigns it to the view property.
     // 这个方法负责 加载 或者 创建一个视图并且把它设置给控制器的view属性!
     
     If the view controller has an associated nib file, this method loads the view from the nib file.
     // 如果这个控制器已经绑定了xib文件,这个方法就会从xib文件中加载视图!
     
     A view controller has an associated nib file if the nibName property returns a non-nil value, which occurs if the view controller was instantiated from a storyboard, if you explicitly assigned it a nib file using the initWithNibName:bundle: method, or if iOS finds a nib file in the app bundle with a name based on the view controller'��s class name.
     // 如果关联了文件,并且真实有效,当控制器是从stroyboard文件中加载时会发生!
     // 如果通过initWithNibName方法绑定了文件,获取系统找了一个基于此控制器类名的文件,都会加载其中的视图!
     
     If the view controller does not have an associated nib file, this method creates a plain UIView object instead.
     // 如果没有关联文件,则直接创建一个普通的UIView对象代替
     
     
     If you use Interface Builder to create your views and initialize the view controller, you must not override this method.
     // 如果使用 IB 去创建视图和以及实例化控制器,禁止重写此方法!
     
     
     You can override this method in order to create your views manually.
     // 如果要手动的创建视图,可以重写此方法!
     
     If you choose to do so, assign the root view of your view hierarchy【层级结构】 to the view property.
     // 如果这么做了,将创建的视图设置给控制器的view属性!
     
     The views you create should be unique instances and should not be shared with any other view controller object.
     // 创建view对象应该是独一无二的,不能和其他控制器共享!
     
     Your custom implementation of this method should not call super.
     // 自己重写的时候不要调用 super
     
    
     If you want to perform any additional initialization of your views, do so in the viewDidLoad method.
     // 如果要实现添加其他的视图操作,可以在viewDidLoad方法中进行!
    

    视图的生命周期

    // 视图加载完成
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        NSLog(@"%@ ---> %s", self.class, __func__);
    }
    
    // 视图将要显示
    - (void)viewWillAppear:(BOOL)animated {
        
        [super viewWillAppear:animated];
        
        NSLog(@"%@ ---> %s", self.class, __func__);
        
    }
    
    // 视图已经显示
    - (void)viewDidAppear:(BOOL)animated {
        [super viewDidAppear:animated];
        
        NSLog(@"%@ ---> %s", self.class, __func__);
        
    }
    
    // 视图即将消失
    - (void)viewWillDisappear:(BOOL)animated {
        [super viewWillDisappear:animated];
        
        NSLog(@"%@ ---> %s", self.class, __func__);
    }
    
    // 视图已经消失
    - (void)viewDidDisappear:(BOOL)animated {
        [super viewDidDisappear:animated];
        
        NSLog(@"%@ ---> %s", self.class, __func__);
    }
    
    ***导航控制器的生命周期
    第一次显示
            OneViewController ---> -[BaseViewController viewDidLoad]
            OneViewController ---> -[BaseViewController viewWillAppear:]
            OneViewController ---> -[BaseViewController viewDidAppear:]
    
        跳转到twoVc
            OneViewController ---> -[BaseViewController viewWillDisappear:]
            TwoViewController ---> -[BaseViewController viewDidLoad]
            TwoViewController ---> -[BaseViewController viewWillAppear:]
            OneViewController ---> -[BaseViewController viewDidDisappear:]
            TwoViewController ---> -[BaseViewController viewDidAppear:]
     
        
        直接返回
            TwoViewController ---> -[BaseViewController viewWillDisappear:]
            OneViewController ---> -[BaseViewController viewWillAppear:]
            TwoViewController ---> -[BaseViewController viewDidDisappear:]
            OneViewController ---> -[BaseViewController viewDidAppear:]
     
        拖拽返回
            TwoViewController ---> -[BaseViewController viewWillDisappear:]
            OneViewController ---> -[BaseViewController viewWillAppear:]
            TwoViewController ---> -[BaseViewController viewDidDisappear:]
            OneViewController ---> -[BaseViewController viewDidAppear:]
    
        拖拽不返回
            TwoViewController ---> -[BaseViewController viewWillDisappear:]
            OneViewController ---> -[BaseViewController viewWillAppear:]
            OneViewController ---> -[BaseViewController viewWillDisappear:]
            OneViewController ---> -[BaseViewController viewDidDisappear:]
            TwoViewController ---> -[BaseViewController viewWillAppear:]
            TwoViewController ---> -[BaseViewController viewDidAppear:]
    
    ***TabBar控制器的生命周期 
           
    第一次显示
            OneViewController ---> -[BaseViewController viewDidLoad]
            OneViewController ---> -[BaseViewController viewWillAppear:]
            OneViewController ---> -[BaseViewController viewDidAppear:]
    
        跳转到twoVc
            OneViewController ---> -[BaseViewController viewWillDisappear:]
            TwoViewController ---> -[BaseViewController viewDidLoad]
            TwoViewController ---> -[BaseViewController viewWillAppear:]
            OneViewController ---> -[BaseViewController viewDidDisappear:]
            TwoViewController ---> -[BaseViewController viewDidAppear:]
     
        
        直接返回
            TwoViewController ---> -[BaseViewController viewWillDisappear:]
            OneViewController ---> -[BaseViewController viewWillAppear:]
            TwoViewController ---> -[BaseViewController viewDidDisappear:]
            OneViewController ---> -[BaseViewController viewDidAppear:]
     
        拖拽返回
            TwoViewController ---> -[BaseViewController viewWillDisappear:]
            OneViewController ---> -[BaseViewController viewWillAppear:]
            TwoViewController ---> -[BaseViewController viewDidDisappear:]
            OneViewController ---> -[BaseViewController viewDidAppear:]
    
        拖拽不返回
            TwoViewController ---> -[BaseViewController viewWillDisappear:]
            OneViewController ---> -[BaseViewController viewWillAppear:]
            OneViewController ---> -[BaseViewController viewWillDisappear:]
            OneViewController ---> -[BaseViewController viewDidDisappear:]
            TwoViewController ---> -[BaseViewController viewWillAppear:]
            TwoViewController ---> -[BaseViewController viewDidAppear:]
    

    视图的生命周期

    // 可以再UIView中,合适的方法中调用比如说定时器之类的东西
    // 将要移动到父视图
    - (void)willMoveToSuperview:(UIView *)newSuperview {
        
        NSLog(@"%@", newSuperview);
    
        NSLog(@"%@ --> %s", self.class, __func__);
    }
    
    // 已经移动到父视图
    - (void)didMoveToSuperview {
        
        [super didMoveToSuperview];
        NSLog(@"%@ --> %s", self.class, __func__);
    }
    
    // 将要移动到窗口
    - (void)willMoveToWindow:(UIWindow *)newWindow {
    
        [super willMoveToWindow:newWindow];
    
        NSLog(@"%@ --> %s", self.class, __func__);
    }
    
    // 已经移动到窗口
    - (void)didMoveToWindow {
        
        [super didMoveToWindow];
        
        NSLog(@"%@ --> %s", self.class, __func__);
    }
    

    内存警告的方法

    // 当系统内存紧张的时候,会触发这个方法!
    // 在项目中,接收到内存警告,需要做的操作:
    1. 清除应用的缓存!"缓存图片"!及时释放内存!
    2. 如果什么都不做,有时候应用就直接被干掉了!
    
    -
     (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];    
        // 如果视图有值,并且不可见
        if (self.view != nil && self.view.window == nil) {        
            // 将视图置为nil
            self.view = nil;     
        }
    }
    

    控制器之间的传值(顺传)

    1.  基于storyboard开发,在跳转的时候会执行这个方法
        通过 [segue destinationViewController] 方法获取新的控制器! 
        destination目的地!
        目标控制器 segue.destinationViewController
        通过控件跳转到时候,sender就是控件本身!
    - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
        NSLog(@"%@", sender);// 触发事件的(对象)按钮
        // 1.取目标vc
        UIViewController *vc = segue.destinationViewController;
        // 2.传递值
        ((TwoViewController *)vc).txtStr = "要传递的内容";
    }
    2. 正常的拿到目标控制器,进行属性赋值操作
    // 创建对象
        CZTwoViewController *twoVc = [[CZTwoViewController alloc] init];    
    // 赋值
        twoVc.text = _txtFld.text;    
    //  跳转
        [self.navigationController pushViewController:twoVc animated:YES];
    

    控制器之间的传值(代理逆传)

    1. 制定协议
        @class TwoViewController;
        @protocol TwoViewControllerDelegate <NSObject>
        @optional
        - (void)twoViewController:(TwoViewController *)vc needsMoney:(NSString *)moneyStr;
        @end    
    2. 代理属性
        @property (nonatomic, weak) id<TwoViewControllerDelegate> delegate; 
    3. 判断,并让代理做事情
        if ([_delegate respondsToSelector:@selector(twoViewController:needsMoney:)]) {
                [_delegate twoViewController:self needsMoney:txt];
            }
                
    4. 设置代理
        ((TwoViewController *)vc).delegate = self;
            
    5. 遵守协议
        @interface OneViewController () <TwoViewControllerDelegate>
    6. 实现方法
    - (void)twoViewController:(TwoViewController *)vc needsMoney:(NSString *)moneyStr {
        NSLog(@"%@", moneyStr);    
    }
    
    

    segue 分手动型和自动型两种

    自动型segue:
        在storyboard文件中。通过按住control键 + 在控件上 拖线 跳转到下一个控制器,为自动型segue。
        只要点击控件,就跳转了。    
        使用简单方便,说走就走,不受程序员控制器!系统会在跳转时执行控制器的prepareForSegue方法,做值的传递工作。
    
    手动型segue:
        在storyboard文件中。通过选择控制器上面的黄色控制器按钮,然后 control + 从控制器脱线跳转的segue,为手动型segue
        执行时机由程序员控制器!更加方便准确! 
        需要通过 performSegueWithIdentifier: 方法实现跳转!注意给segue加标记!
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        // 这句代码可以放到任意你想要进行判断条件和方法中
        [self performSegueWithIdentifier:@"one" sender:nil];
    }
    
    - (void)performSegueWithIdentifier:(NSString *)identifier sender:(id)sender{
        // 这里判断indentifier是不是"one",然后进行对应的跳转逻辑
    }
    

    应用的bundle[包]

    // 1.获取应用的包路径[bundle -> 在mac中的位置]
    说明:应用的包路径,里面存放了应用的代码可执行文件,及其他的资源文件!
    // 一般程序员不对其进行操作!
        NSString *bundlePath = [NSBundle mainBundle].bundlePath;
        
    // 2.获取应用的info.plist文件的内容
        NSDictionary *infoDict = [NSBundle mainBundle].infoDictionary;
    

    沙盒

    // 3.获取沙盒路径 NSHomeDirectory()
    沙盒路径【主路径】
    应用的沙盒主目录,默认有Document/Library/temp三个子目录
    可以进行存储和读取数据操作!  
        NSString *sandBoxPath = NSHomeDirectory();        
    

    获取documents路径 文档目录

         参数1: 文档目录名称,不能写NSDocumentionDirectory[错误写法]
         参数2: 用户权限,固定 NSUserDomainMask
         参数3: 是否展开波浪线,固定 YES
         - 文档目录,可以用于存储一些比较重要的数据,由应用程序生成的数据!
         - 在备份的时候,会备份此目录!
    NSString *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;  
    

    获取缓存路径

    说明:- 缓存目录,通常保存从网络上下载的需要持久化的数据
         - 备份时,不会备份该目录,并且不会自动删除数据,需要程序内部提供删除的操作!
    NSString *cachePath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).lastObject;
    

    获取tmp路径 临时文件目录!

    说明:- 临时文件目录,系统会自动删除其中的文件
         - 用于存储临时文件时使用!
    NSString *tmpPath = NSTemporaryDirectory();        
    

    plist

    数据存储为plist文件
    NSDictionary *dict = @{@"name" : @"wuqi",@"age" : @18};
    NSString *filePath = [self filePathWithFileName:@"文件.plist"];
         参数1: 文件路径
         参数2: 原子属性-> 设置为YES,更安全!
         -> 即使应用崩溃,存储也不受影响!
         -> 保证写入的唯一性!
    [dict writeToFile:filePath atomically:YES];
    
    读取plist文件
    NSString *filePath = 文件路径;
    // 看plist文件是什么类型和结构.对应的进行读取
    [NSDictionary dictionaryWithContentsOfFile:filePath];
    [NSArray arrayWithContentsOfFile:filePath]
    

    NSString

    如果字符串中有 "/",就不再增加斜杠,如果没有,自动增加"/"
    [@"abc" stringByAppendingPathComponent:@"123.text"];
    @"abc/123.text"
    
    

    NSURL

    这种方式加载的文件是我们直接拖入的,在bundle里面的文件!
    NSURL *url = [[NSBundle mainBundle] URLForResource:@"xxx.plist" withExtension:nil];
    从磁盘文件中读取的时候用这个方法 仅供参考[加载的是磁盘中文件的路径转为url]
    NSURL *url = [NSURL fileURLWithPath:filePath];
    

    NSUserDefaults 单例 偏好设置 -> 负责偏好设置存储及读取操作!

    存
    // 1.获取用户偏好设置对象
        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    // 2.存储数据
        [defaults setBool:isMan forKey:@"LWIsMan"];
        [defaults setFloat:fontSize forKey:@"LWFontSize"];
        [defaults setObject:str forKey:@"LWStr"];
    // 3.同步 - > 保证数据立即写入磁盘!目前已经不需要了! 
    同步操作,保证数据立即被写入磁盘,老项目中经常会见到,将要被废弃。
    以后在写偏好设置时,可以不同同步,但是见到需要了解其含义!
    //    [defaults synchronize];不写
        
    取
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    BOOL man = [defaults boolForKey:@"LWIsMan"];
    float size = [defaults floatForKey:@"LWFontSize"];
        NSString *str = [defaults stringForKey:@"LWStr"];    
    

    数据存储[归档&解档]

    归档
    使用归档存储自定义对象的操作
    进行归档操作时,需要自定义对象的类型 需要NSCoding协议!
    实现归档方法! -> 告诉系统按照什么原则进行归档![标准!]
    
    1. 遵守协议! <NSCoding>
    @interface Person : NSObject <NSCoding>
    @property (copy, nonatomic) NSString *name;// 姓名
    @property (assign, nonatomic) NSInteger age;// 年龄
    @property (copy, nonatomic) NSString *phoneNumber;// 电话
    
    2. NSCoding中的协议方法 -> 通过aCoder设定对应的归档原则!
     - (void)encodeWithCoder:(NSCoder *)aCoder {    
        [aCoder encodeObject:_name forKey:KeyName];
        [aCoder encodeInteger:_age forKey:KeyAge];
        [aCoder encodeObject:_phoneNumber forKey:KeyPhoneNumber];
    }
    // NSKeyedArchiver 负责归档数据!
    // 存储的对象
    // 文件路径中如果带上文件名对应的后缀,没有什么实际意义! 在mac中,写后缀,系统根据后缀选择一个默认的应用进行打开!
        [NSKeyedArchiver archiveRootObject:存储的对象 toFile:"文件路径"];
    // !! 也可以直接归档实现NSCoding协议的归档对象的数组
    NSArray *arr = @[person1, person2];
    [NSKeyedArchiver archiveRootObject:arr toFile:"文件路径"];    
    
    解档    
    NSCoding中的协议方法 -> 通过aDecoder设置对应的解档原则
    // 解码出来的数据赋值给谁? -赋值给对象本身
    - (instancetype)initWithCoder:(NSCoder *)aDecoder {    
        if (self = [super init]) {        
            // 读取数据信息
            _name = [aDecoder decodeObjectForKey:KeyName];
            _phoneNumber = [aDecoder decodeObjectForKey:KeyPhoneNumber];
            _age = [aDecoder decodeIntegerForKey:KeyAge];
        }
        return self;
    }
        
    通过NSKeyedUnArchiver 读取数据
    [NSKeyedUnarchiver unarchiveObjectWithFile:"文件路径"];
    

    iOS中的三大事件!

        加速计事件[摇一摇]! 
        远程控制事件[耳机]!
        [触摸事件!]
    

    响应者

        Respond 响应!
        在iOS中,所有继承UIResponder类型的对象,都是响应者对象!
        我们常用控件,控制器,应用程序大家都是响应者对象
        UIWindow -> 继承自UIView 也是一个响应者对象!
    

    NSSet

    NSSet 类型的 touches  -> NSSet也是一个集合里面存放的是UITouch类型的对象!
    
    NSSet也是一个集合
        > 没有顺序!
        > 通过anyObject获取集合中的元素
        > 遍历
        i for 循环!不可以!因为没有顺序!
            -> 只能通过forIn进行遍历
            -> 通过block进行遍历!
    

    手势触摸Touche

    // 触摸开始
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        // 1.触摸点 -> self.superView 获取相对于父视图来说的坐标点
        CGPoint loc = [touches.anyObject locationInView:self.superview];    
        // 2.修改中心点
        self.center = loc;    
    }
    
    // 触摸移动 -> 移动一个视图
    - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        // 1.获取当前点
        CGPoint loc = [touches.anyObject locationInView:self];    
        // 2.上一次的点
        CGPoint preLoc = [touches.anyObject previousLocationInView:self];
        // 3.计算偏移量
        CGFloat offsetX = loc.x - preLoc.x;
        CGFloat offsetY = loc.y - preLoc.y;    
        // 4.修改位置
        self.center = CGPointMake(self.center.x + offsetX, self.center.y + offsetY);
    }
    

    坐标转换

    // 进行坐标转换的方法!
        [self convertPoint:loc toView:self.superview]
    

    控件不接受用户交互的情况

        1.透明度 <=0.01
        2.hidden = YES
        3.userInteractionEnable = NO用户交互关闭
        4.控件的父控件不接受用户交互
        5.控件超出了父控件的区域
        6.UIImageView默认是关闭用户交互的!添加需要做事情的控件时需要打开用户交互!
        
    如果遇到某些控件,比如说按钮,点了之后没反应,或者列表视图不能滚动了!
        // 1.看当前控件的用户交互是否被关闭?
        // 2.看当前控件的父控件用户交互是否被关闭?
        // 3.看当前控件显示的位置,是否超出了父控件!
        // 4.看当前控件的父控件,是否超出了父控件的范围!
    

    手势识别

    手势识别器的基本结构:
        UITapGestureRecognizer;         // 轻点手势识别器
        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapActionClick:)];
        
        UILongPressGestureRecognizer;   // 长按手势识别器
        UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressAction:)];
        
        UIRotationGestureRecognizer;    // 旋转手势识别器
        UIRotationGestureRecognizer *rotation = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(rotationAction:)];
        // 响应事件中
        旋转的角度会一直类加,所以旋转事件结束后将旋转的弧度清零
        旋转最后需要将手势旋转角度清零
        rotation.rotation = 0;
        
        UIPinchGestureRecognizer;       // 捏合手势识别器
        UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchAction:)];
        // 响应事件中
        // 缩放结束后将手势缩放比设置为1
        pinch.scale = 1.0;
        
        UISwipeGestureRecognizer;       // 清扫手势识别器
        // 添加一个向右扫动的手势
        UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeAction:)];
        // 设置支持的扫动方向 -> 默认只支持一个方向,向右扫动时触发。
        swipe.direction = UISwipeGestureRecognizerDirectionLeft;
        
        UIPanGestureRecognizer;         // 拖拽手势识别器
        // 1.实例化拖拽手势识别器
        UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panAction:)];
        // 响应事件中
        // 计算移动的距离
        CGPoint offset = [pan translationInView:pan.view];
        // 将手势识别器的移动距离恢复为0
        [pan setTranslation:CGPointZero inView:pan.view];
                
    手势识别器的基本用法:
        1. 实例化手势识别器,指定响应的方法
        2. 将手势识别器添加到对应的view内
        3. 实现相应的方法,在方法内执行需要的操作
                    
    如果实现多手势操作:
        1. 设置手势识别器的代理
        <UIGestureRecognizerDelegate>
        2. 实现相应的代理方法,支持多手势识别。    
    - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
        return YES;
    }    
    
    // 1.实例化手势识别器: 例如点按
        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapActionClick:)];
    // 1.2设置一些公共的属性
        tap.numberOfTapsRequired = 2;//要按几下才会响应
        tap.numberOfTouchesRequired = 2;//要有几个手指按下才会响应
    // 2.将手势识别器添加到view里面
        [self.headerView addGestureRecognizer:tap];    
        
    // UIPanGestureRecognizer.state
    UIGestureRecognizerStateBegan // 开始
    UIGestureRecognizerStateChanged // 改变
    UIGestureRecognizerStateEnded //结束
    UIGestureRecognizerStateFailed // 失败
    UIGestureRecognizerStateCancelled // 取消
    

    事件的产生和传递

    -> 递归;牛逼的编程思想! -> 条件满足,直接返回,条件不满足,自己调用自己,参数有改变!
    
    // 响应者链条!
        有一系列的 "响应者对象"所"组成"的"链式序列"的,叫做"响应者链条"!
        
    
    // 碰撞测试,模拟系统查找视图的过程
    // hitTest方法,由于内部算法的原因默认时执行两次的
    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
        NSLog(@"%@ --> %s", self.class, __func__);
        // 1.如果点不在视图上,直接返回
        if (![self pointInside:point withEvent:event]) {
            return nil;
        }
        // 2.如果不允许用户交互,返回nil
        if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) {
            return nil;
        }
        // 3.点在身上并且自己接受用户交互
        for (int i = (int)self.subviews.count - 1; i >= 0; i--) {
            // 3.1 取出最后一个视图
            UIView *lastV = self.subviews[i];
            // 3.2 转换坐标
            CGPoint subPoint = [self convertPoint:point toView:lastV];
            // 3.3 子视图里面接着找
            UIView *nextView = [lastV hitTest:subPoint withEvent:event];
            // 3.4 如果找到了,就返回
            if (nextView) {
                return nextView;
            }
        }
        return self;
    }
    // 测试的结果和这个结果一致
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        NSLog(@"%@ --- %s", self.class, __func__);
    }
    
        查找的过程
        应用程序对象->窗口->控制器->控制器根视图->最后一个子控件->子控件内部的子控件->返回该子控件
            应用程序对象->窗口->最后一个子控件->子控件内部的子控件->返回该子控件
    
    

    UIContainerView

    UIContainerView 与 静态单元格类似,都是storyboard特有的。
    可以作为视图容器直接拿来使用!
    
    可以在UIContainerView上添加点按和拖拽来实现QQ侧滑的效果.
    UIContainerView 就是在界面添加一个View.View里面关联的是一个控制器
    

    父控制器添加子控制器

    - (void)addChildViewController:(UIViewController *)childController
    - (void)removeFromParentViewController
    - (void)transitionFromViewController::::::
    - (void)willMoveToParentViewController:(UIViewController *)parent
    - (void)didMoveToParentViewController:(UIViewController *)parent
    
    关于willMoveToParentViewController方法和didMoveToParentViewController方法的使用
    
    1.这两个方法用在子试图控制器交换的时候调用!即调用transitionFromViewController 方法时,调用。
    
    2.当调用willMoveToParentViewController方法或didMoveToParentViewController方法时,要注意他们的参数使用:
    
    当某个子视图控制器将从父视图控制器中删除时,parent参数为nil。
    即:[将被删除的子试图控制器 willMoveToParentViewController:nil];
    
    当某个子试图控制器将加入到父视图控制器时,parent参数为父视图控制器。
    即:[将被加入的子视图控制器 didMoveToParentViewController:父视图控制器];
    
    3.无需调用[子视图控制器 willMoveToParentViewController:父视图控制器]方法。因为我们调用[父视图控制器 addChildViewController:子视图控制器]时,已经默认调用了。
    只需要在transitionFromViewController方法后,调用[子视图控制器didMoveToParentViewController:父视图控制器];
    
    4.无需调用[子视图控制器 didMoveToParentViewController:父视图控制器]方法。因为我们调用
    [子视图控制器 removeFromParentViewController]时,已经默认调用了。
    只需要在transitionFromViewController方法之前调用:[子视图控制器 willMoveToParentViewController:nil]。
    

    静态分析

    Performing Static Code Analysis
    // 执行静态代码分析
    
    Find flaws—potential bugs—in the source code of a project with the static analyzer built into Xcode. Source code may have subtle errors that slip by the compiler and manifest themselves only at runtime, when they could be difficult to identify and fix.
    
    // 查找缺陷-潜在的bug-在项目的源代码中使用Xcode内置的静态分析工具。
    // 源代码可能会存在一些很难被发现和修复的细微的错误,只有在运行的时候才能够被发现。
    
    Steps
        1   Choose Product > Analyze.
    
        2   In the issue navigator, select an analyzer message.
    
        3   In the source editor, click the corresponding message.
    
        4   Use the pop-up menu in the analysis results bar above the edit area to study the flow path of the flaw. 
    
        5   Edit the code to fix the flaw.
    
    步骤:
    // 1.选择 product -> Analyze
    // 2.在问题的导航器上,选择静态分析工具的信息。
    // 3.在源代码编辑器上,点击相应的信息
    // 4.在位置编辑区域顶部的菜单中查看错误的错误原因。
    // 5.编辑代码修复缺陷。
    
    
    The Xcode static analyzer parses the project source code and identifies these types of problems:
        •   Logic flaws, such as accessing uninitialized variables and dereferencing null pointers
        •   Memory management flaws, such as leaking allocated memory
        •   Dead store (unused variable) flaws
        •   API-usage flaws that result from not following the policies required by the frameworks and libraries the project is using
    
    // Xcode静态分析工具能够分析项目的源代码并且确定以下类型的问题:
    // 1> 逻辑缺陷,如访问没有初始化的变量和非关联化的空指针
    // 2> 内存管理缺陷,例如内存泄露问题
    // 3> 没有任何用处变量缺陷
    // 4> API使用缺陷,由于在项目中使用的框架或库没有遵守相关的要求而导致的错误
    
    You can suppress false positive messages from the analyzer using assertions, attributes, or pragma directives.
    // 你可以使用断言,属性,或编译指令抑制分析工具中报出的假消息。
    
    When you analyze a project for the first time, you may uncover a lot of issues. But if you run the static analyzer regularly and fix the flaws it uncovers, you should see fewer problems in subsequent analyses. Analyze early; analyze often. It’s good for the code.
    // 当第一次分析一个项目的时候,可能会发现很多问题。但是如果在开发中经常的进行静态分析并且及时修复发现的问题,在以后的分析中问题就会越来越少。
    // 早发现,早治疗。对代码也是好的!
    
    Note that if the static analyzer reports no problems, you can't assume that there are none. The tool cannot necessarily detect all the flaws in the source code.
    // 注意:如果静态分析工具没有报告任何问题,你不能假定认为就没有问题了。这个工具不能保证说任何在源代码中的问题都能够被准确发现。
    
    

    基本图形绘制-Quartz2D绘图

    1 如果需要向视图中进行图形绘制
        - 要重写视图的drawRect方法,在其中编写绘图代码
     
    2 绘图的基本步骤包括:
        - 获取当前的图形上下文
        - 向上下文中添加路径
        - 渲染,【将上下文中的路径渲染到屏幕上并显示出来】
        
    绘制三角形
        // 1.获取图形上下文
        // 一个Quartz2D的绘图环境!
        CGContextRef cxt = UIGraphicsGetCurrentContext();
        // 2.路径
        // - 移动到起点
        CGContextMoveToPoint(cxt, 50, 50);
        // - 添加线
        CGContextAddLineToPoint(cxt, 200, 50);
        // - 第二根线
        CGContextAddLineToPoint(cxt, 50, 200);
        // - 第三根线
        // CGContextAddLineToPoint(cxt, 50, 50);
        // 关闭路径:将路径的 终点 和 起点 进行连线!
        CGContextClosePath(cxt);
        
        // 3.渲染
        CGContextFillPath(cxt);// fill -> 负责填充
        CGContextStrokePath(cxt);//stroke -> 只负责画线    
       
    绘制矩形
        // 1.图形上下文
        CGContextRef cxt = UIGraphicsGetCurrentContext();
        // 2.路径
        CGContextAddRect(cxt, CGRectMake(50, 50, 200, 100));
        // 3.渲染
        CGContextStrokePath(cxt);
        
    绘制圆形
        // 1.上下文
        CGContextRef cxt = UIGraphicsGetCurrentContext();
        // 2.圆形
        // 绘制一个内切与矩形区域的圆形
        // - 如果矩形恰好宽高相等,则绘制出来为圆形
        // - 如果矩形宽高不相等,则绘制出来的时椭圆
        CGContextAddEllipseInRect(cxt, CGRectMake(50, 50, 200, 100));
        
        // 直接绘制圆形的方式
         x,y ->     确定圆心的位置
         radius ->  半径
         startA ->  起始角度
         endA ->    结束角度
         clockwise -> 绘制方向 1 顺时针 0 逆时针
        CGContextAddArc(cxt, 150, 150, 75, 0, M_PI, 0);
        
        // 3.渲染
        CGContextStrokePath(cxt);        
    
    绘制一组点组成的图案 <五角星>
        CGContextRef cxt = UIGraphicsGetCurrentContext(); 
        
        CGPoint points[] = {
            CGPointMake(158, 50),
            CGPointMake(191.37, 117.15),
            CGPointMake(266, 127.92),
            CGPointMake(212, 180.19),
            CGPointMake(224.75, 254),
            CGPointMake(158, 219.15),
            CGPointMake(91.25,254),
            CGPointMake(104,180.19),
            CGPointMake(50, 127.92),
            CGPointMake(124.63, 117.15),
            CGPointMake(158, 50)
        };
        
        CGContextAddLines(cxt, points, 数组元素个数);
        CGContextStrokePath(cxt);
        
    线条属性
        // 线宽
        // 从中间计算向两边分开
        CGContextSetLineWidth(cxt, 20);
        // 线头样式
        kCGLineCapButt, 默认的样式!比较难看!
         kCGLineCapRound,圆角!
         kCGLineCapSquare平角!
        CGContextSetLineCap(cxt, kCGLineCapRound);
        // 接头样式
        kCGLineJoinMiter 尖角!
         kCGLineJoinRound,圆角!
         kCGLineJoinBevel 倒角!
        CGContextSetLineJoin(cxt, kCGLineJoinRound);
        // 颜色
        [[UIColor whiteColor] setStroke];    
    
    条形码
        // 1. 上下文
        CGContextRef cxt = UIGraphicsGetCurrentContext();
        // 2. 路径
        CGContextMoveToPoint(cxt, 0, 100);
        CGContextAddLineToPoint(cxt, 300, 100);
        CGContextSetLineWidth(cxt, 100);
        // 虚线
        /**
         phase: 阶段,可以写0
         lengths 数组
         count   数组内元素的数量
         */
        CGFloat lengths[] = {3,1,4,1,5,9,2,6,5,3,5,4,5,8,5,9,9,7,1,10};//一个线的颜色,一个填充颜色.交替
        CGContextSetLineDash(cxt, 0, lengths, (sizeof(lengths) / sizeof(CGFloat)));//C语言求数组的长度
        // 3. 渲染
        CGContextStrokePath(cxt);   
         
    

    使用Quartz2D内存管理问题

    内存管理问题
    在C语言函数中,遇到了create或copy或retain!这些关键单词创建的对象,最后结束的时候,需要自己release!
    ARC -> 自动内存管理针对的是oc!  
    
    获取图形上下文
        CGContextRef cxt = UIGraphicsGetCurrentContext();
    创建路径!
        CGMutablePathRef path = CGPathCreateMutable();    
        CGPathMoveToPoint(path, NULL, 50, 50);
    添加线!
        CGPathAddLineToPoint(path, NULL, 250, 250);
    给上下文中添加一条路径 CGPathRef类型路径!
        CGContextAddPath(cxt, path);
    渲染
        CGContextStrokePath(cxt);
    释放路径资源
        CGPathRelease(path);
        
    释放对象的方式! CGPathRelease(对象)
    CFRelease(对象);//可以释放任何对象! 有时候也不给力! 
    

    填充规则

    填充规则
        非零绕数填充原则 fill!  
            a.视图中的一个点
            - 如果被顺时针覆盖,记为1
            - 如果被逆时针覆盖,记为-1
            如果结果为0,则不渲染,否则渲 
            这个规则与方向有关,与次数无关
        CGContextDrawPath(cxtRef, kCGPathFill)
                
        奇偶填充原则 Eofill 
            a.视图中的一个点
            - 如果被覆盖奇数次,渲染;
            - 如果被覆盖偶数次,不渲染;
    
        // 同时显示stroke和fill的颜色
        CGContextDrawPath(cxtRef, kCGPathEOFill);
            
    // 1.绘制圆环
        // 获取上下文
        CGContextRef cxtRef = UIGraphicsGetCurrentContext();
        // 圆形1
        CGPoint center = CGPointMake(150, 150);
        CGFloat radius = 150;
        // 逆时针!
        CGContextAddArc(cxtRef, center.x, center.y, radius, 0, M_PI * 2, 1);
        // 圆形2
        // 顺时针
        CGContextAddArc(cxtRef, center.x, center.y, radius - 20, 0, M_PI * 2, 0);
        // 渲染! -> 填充!
        CGContextFillPath(cxtRef);  
        
    

    drawRect方法介绍

        1 rect & 图形上下文
     - 当前视图的bounds!
     - 图形上下文的本质:一套Quartz2D的绘图环境,可以实现不同图形的绘制!
     
     2 drawRect方法调用时机:
        - 视图在第一次显示的时候会调用一次drawRect方法
        - 系统内部自动执行的,不允许程序员手动直接调用,可以保证上下文的顺利获取!
     
     3 如果需要进行重新绘制图形如何实现?
        - 可以通过setNeedsDisplayInRect:方法实现重绘【极少用】
        - 可以通过setNeedsDisplay方法实现重绘【常用】
        - 好处:间接实现重绘操作,系统内部帮我们执行drawRect方法,保证上下文的正常使用!   
    

    UIKit绘图绘制文字

    CoreGraphics核心绘图框架,进行图形绘制的!C语言形式的框架!
    UIKit框架里面的内容展示过程都是依靠核心绘图框架展示内容!两者的关系很紧密!
    
    - (void)drawRect:(CGRect)rect {
    // 1.字符串
       NSString *str = @"Hello World";
       // 2.绘制
       NSDictionary *dict = @{    // 设置字体的大小
                              NSFontAttributeName : [UIFont systemFontOfSize:22],
                                  // 设置文字的颜色
                              NSForegroundColorAttributeName : [UIColor magentaColor]};
        // 3 从某个点开始绘制
        [str drawAtPoint:CGPointMake(0, 100) withAttributes:dict];
        // 在某个区域进行绘制
        [str drawInRect:CGRectMake(0, 0, 200, 100) withAttributes:dict];
    }    
    

    UIKit绘制图片

    - (void)drawRect:(CGRect)rect {
        // 1.图片
        UIImage *img = [UIImage imageNamed:@"me"];
        // 2.绘制图片
        // 2.1 从某个点开始画图(大小不变)
        [img drawAtPoint:CGPointMake(100, 100)];
        // 2.2 在某个区域开始画图(大小会变)
        [img drawInRect:CGRectMake(0, 0, 200, 200)];
        // 2.3 以平铺的方式画图(大小不变)
        [img drawAsPatternInRect:self.bounds];
    }    
    

    bezierpath路径绘图<贝塞尔>

    - (void)drawRect:(CGRect)rect {
        // 1.创建路径
        UIBezierPath *path;
        // 矩形
        path = [UIBezierPath bezierPathWithRect:CGRectMake(50, 50, 200, 100)];    
        // 圆角矩形
        path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(50, 50, 200, 100) cornerRadius:20];    
        // 椭圆
        path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(50, 50, 200, 100)];    
        // 弧形
        // 参数 圆心 | 半径 | 起始角度 | 结束角度 | 是否为顺时针
        path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(150, 150) radius:70 startAngle:0 endAngle:M_PI_4 clockwise:YES];    
        // 2.渲染
        [path stroke];   
    }
    

    截图保存图片

    // 1. 获取图片
    需要开启图片的图形上下文! -> 可以生成一张图片!
    生成的图片更清晰! 缩放因子!
    size -> 图片上下文的大小!
    opaque -> 不透明,设置为 NO -> 透明!
    scale -> 缩放因子!
        UIGraphicsBeginImageContextWithOptions(self.bounds.size, YES, 0);    
        
    Hierarchy 层次结构!
    将当前视图的层次结构渲染到上下文中!
    updates -> 是否更新? -> tableView在滚动的过程,截图!保证不会有不清晰的情况! 
        [self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES];    
        
    获取图片
        UIImage *result = UIGraphicsGetImageFromCurrentImageContext();    
    关闭图形上下文,一定要关闭
        UIGraphicsEndImageContext();    
    
    // 2. 保存到相册
        UIImageWriteToSavedPhotosAlbum(result, self, @selector(image:didFinishSavingWithError:contextInfo:), @"图片名称");
        
    // 3. 保存图片结束后系统会自动调用的方法
    - (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo {
        NSLog(@"图片保存成功");
    }   
    // 4. info.plist文件中添加
    NSPhotoLibraryUsageDescription 提示字段否则不合法会崩溃 
           
    

    图形绘制[实战]

    1. 柱状图📊
        思路:在于如何求出柱状图的左上角坐标以及柱状图的宽度
        然后进行贝塞尔绘制即可
    2. 饼状图
        思路:在于如何求出开始和结束的角度
        然后进行贝塞尔绘制即可
        记得向圆心画线保证生成扇形
        [path addLineToPoint:center];
    3. 进度饼状图
        思路:根据进度获取到开始和结束的角度
        将弧形起点向圆心连线!
        [path closePath];   
    

    自定义属性

    IB_DESIGNABLE
    // 让视图可以在storyboard文件中进行编辑
    
    // IBInspectable 可以保证当前属性可以在storyboard中进行编辑![需要先添加,然后保证keyPath和属性一致Type类型正确]
    @property (nonatomic, strong) IBInspectable UIColor *lineColor;
    

    手势解锁思路

    1. 界面布局,九宫格布局
        // 宽、高
        CGFloat btnW = 74;
        CGFloat btnH = 74;
        int colums = 3;//固定列数
        // 间距
        CGFloat margin = (self.bounds.size.width - colums * btnW) / 2;
        int i = 0;
        for (UIButton *btn in self.subviews) {
            // 1.计算行号、列号
            int row = i / colums;
            int col = i % colums;
            // 2.计算按钮的x
            CGFloat btnX = col * (btnW + margin);
            CGFloat btnY = row * (btnH + margin);
            // 3.设置按钮的frame
            btn.frame = CGRectMake(btnX, btnY, btnW, btnH);
            i++;
        }
    2. 添加长按手势识别器
    3. 获取触摸点,判断这个触摸点是哪个按钮上面的.如果在就将按钮保存到一个数组中,需要判断不要让按钮重复添加
    // 判断点是不是在一个范围内
    CGRectContainsPoint(CGRect, CGPoint)
    4. 绘制.以第一个按钮的中心为起始点,其他按钮的中心点为点进行画线,最后一根线根据当前的触摸点绘制
    5. 手势结束,根据Button的tag.拼接密码进行验证.
    6. 第一次验证,进行偏好设置进行存储
    7. 验证成功过,切换控制器,提示欢迎语
    8. 验证失败让View视图,按钮恢复默认状态.手势数组中的按钮移除,重新绘制
    

    视图创建两种方法

    如果视图是通过代码 alloc + init的形式创建的
         - (instancetype)init
         {
            self = [super init];
                if (self) {
            }
            return self;
         }
         
         - (instancetype)initWithFrame:(CGRect)frame
         {
            self = [super initWithFrame:frame];
                if (self) {
            }
            return self;
         }
    如果视图是通过storyboard关联的哪个视图,
    或者是xib文件中的视图,执行下面两个方法!     
         - (instancetype)initWithCoder:(NSCoder *)coder
         {
            self = [super initWithCoder:coder];
                if (self) {
            }
            return self;
         }
         
         // awake完全唤醒 -> 可以保证类里面属性与文件中的控件的连线,生效!
         - (void)awakeFromNib {
         }
    

    图形上下文的其他操作

    矩阵操作
    就是这些咯.对上下文进行,旋转,缩放,平移等.
    1.旋转
        // — 旋转  
        CGContextRotateCTM(txt, M_PI_4/2);
            以左上角为轴!
    2.缩放
        // — 缩放
        CGContextScaleCTM(txt, 0.5, 0.5);
            以左上角为轴!
    3.平移
        // — 平移
        CGContextTranslateCTM(txt, 20, 50);
            水平和垂直方向的移动!
            
    栈操作
    与矩阵操作配合使用!
    说白了就是压栈,然后出栈.那么还是原来的上下文.你可以继续进操作了又
    1.在执行矩阵操作前,先保存一份到图形上下文的[栈]中
        栈操作 -> 执行矩阵操作之前先保存
        CGContextSaveGState(txt);
    2.在下次需要使用默认状态的时候,[出栈]操作就可以恢复之前保存的状态
        栈操作 -> 恢复状态
        CGContextRestoreGState(txt);    
        
    裁剪
        //1.画圆
         CGContextAddEllipseInRect(ctx,CGRectMake(100,100,50,50));
        //2.裁剪
        CGContextClip(ctx);
        //3.显示图片
        UIImage *image = [UIImage imageNamed:@"me"];
        [image drawAtPoint:CGPointMake(100,100)];
        CGContextFillPath(ctx); 
    

    图片三维透视效果

        // 1.单位矩阵
        CATransform3D transform = CATransform3DIdentity;
        // 1.2 增加透视效果
        // m34 参数可以实现透视效果!
        transform.m34 = -1.0/500;
        // 2.旋转60度 x,y,z轴分别乘以60度
        transform = CATransform3DRotate(transform, M_PI / 3, 0, 1, 0);
        // 3.修改iv
        imageView.layer.transform = transform;
    

    CALayer

    1.手动创建calayer
        // 1.创建
        CALayer *redL = [CALayer layer];
        // 2.设置大小
        redL.bounds = CGRectMake(0, 0, 100, 100);
        // 3.设置位置 -> 不是center! position位置! 我们给视图设置的center!最终是交给了layer的position的!
        redL.position = CGPointMake(150, 180);
        // 4.背景 -> 需要转为CGColorRef类型的颜色!
        redL.backgroundColor = [UIColor redColor].CGColor;
        // 5.添加
        [self.view.layer addSublayer:redL];
        
    属性
    // MARK: - 1.边框 -> 向里面走!
        _redLayer.borderColor = [UIColor yellowColor].CGColor;
        _redLayer.borderWidth = 10;
        
    // MARK: - 2.阴影 -> shadow
        // 阴影默认会向上偏移3个点! offset
        _redLayer.shadowOpacity = 1.0;
        // 阴影的颜色
        _redLayer.shadowColor = [UIColor blueColor].CGColor;       
        // 阴影的偏移[基于原来的位置]
        _redLayer.shadowOffset = CGSizeMake(100, 100);
        // 阴影半径
        _redLayer.shadowRadius = 50;
        // 阴影的形状可以指定为贝塞尔曲线
        _redLayer.shadowPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, 200, 100)].CGPath;
        
    // MARK: - 3.内容
    // 还可以直接设置背景图片!
        _redLayer.contents = (__bridge id)[UIImage imageNamed:@"name"].CGImage;    
    
    // MARK: - 4.圆角
        _redLayer.cornerRadius = 20;    
        _redLayer.masksToBounds = YES;  
        masksToBounds 这个方法会将bounds外面视图都切掉.看不见所有的效果[比如:阴影]
    
    layer的transform属性!
         参数1 自己的transform
         参数2 旋转的角度
         参数 3/4/5 代表在对应轴上的值! 
         -> 有它们确定一个点!点向圆心连线,组成对应的旋转的轴!
        _redLayer.transform = CATransform3DRotate(_redLayer.transform, M_PI_4, 0, 0, 1)
        // sx sy, sz 代表的是,在每个轴上缩放的比例!
        _redLayer.transform = CATransform3DScale(_redLayer.transform, 0.6, 0.6, 1);    
        // 平移
        _redLayer.transform = CATransform3DTranslate(_redLayer.transform, 0, 0, 100);
     
     
    layer 的隐式动画    
        // 1.开启
        [CATransaction begin];
        // 2.设置关闭 设置为YES,可以关闭隐式动画![流程度和渐变的效果]
        [CATransaction setDisableActions:NO];
        // 隐式动画默认的时长 0.25s!
        // 3.设置动画时间
        [CATransaction setAnimationDuration:1];
        // 4.设置动画完成之后需要执行的操作
        // 动画结束后,会执行block中的代码!
        [CATransaction setCompletionBlock:^{
            NSLog(@"动画结束了");
        }];
        // 5.设置时间曲线[线性的] touchMove的时候流畅一些
        [CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
        // 6. 可以修改layer的属性.transform,position等
        // 7. 提交动画
        [CATransaction commit];    
        
    在修改[手动创建的layer]的时候,有些属性默认是有动画效果的! 注释里面带着animatable! 在修改的时候就有动画!
    这些属性被称为可动画属性! 
    在修改rootLayer[视图里面默认带的layer属性],在修改的时候是没有动画效果的!    
    

    获取系统当前的时间

        // 1.创建日历对象  Calendar 日历!
        NSCalendar *calendar = [NSCalendar currentCalendar];
        // 2.获取时间
        NSInteger second = [calendar component:NSCalendarUnitSecond fromDate:[NSDate date]];
    

    CALayer的Position和AnchorPoint

    position -> 位置!相对于父layer来说的一个位置!默认是中心点
    anchorPoint -> 锚点,定位点!取值范围 0 ~ 1;锚点决定了layer身上的哪个位置,停在了position!
    如果修改了锚点,将来旋转的轴,就变了! -> 参考秒针的效果!
    
    将自己身上的一个点[锚点]放到position[位置]上.
    

    CADisplayLink 和 NSTimer

        [NSTimer scheduledTimerWithTimeInterval:时间间隔 target:对象 selector:@selector(对象调用的方法) userInfo:nil repeats:是否重复];
    
        CADisplayLink *link =[CADisplayLink displayLinkWithTarget:对象 selector:@selector(对象调用的方法)];
        [link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
        
    // MARK: - 1 NSTimer 和 CADisplayLink 对比
        // 两者都是定时器,都可以执行刷新操作!
        
    // 不同之处!
        - timer 可以不用添加到运行循环就可以执行!
        - 可以指定时间间隔进行刷新! 精度稍微低一点!
        - tolarance -> 容忍
        系统如果比较繁忙,timer的刷新可以缓缓!    
        - link 必须添加到运行循环!
        - link 刷新是跟着屏幕的刷新而刷新! 精确度更高!    
    // 如何选择?
        99% 的情况都是使用timer!    
        如果对精确度要求比较高,那可以用link!
    
    定时器的销毁
    如果是在UIView中设置的定时器可以在willMoveToWindow里面去销毁!    
    

    核心动画

    核心动画框架 -> QuartzCore!
        CoreAnimation!
        // 核心动画,都是在后台执行的!性能比较好!
        // 所有的UI界面更新,都是在主线程执行!
        // 不在主线程的可以理解成在后台!
        
        CAAnimation [基类,抽象类[鸡肋]] -> 负责提供其他子类共有的属性和方法!不做具体行为!   
            1. CAPropertyAnimation -> 属性动画!
                1.1 CABasicAnimation -> 基本动画
                1.2 CAKeyFrameAnimation -> 关键帧动画        
            2. CAAnimationGroup -> 组动画        
            3. CATransition -> 转场动画!        
    

    核心动画基本用法 🌰🌰

        - 创建动画对象
        - 设置属性 [关键值!]
        - 将动画对象添加给layer!
        
        // 1.创建基本动画对象 Basic 基本
        // keyPath: -> 想要修改的layer的那个属性!写法,有样板!
        CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"position.y"];
        // 2.设置属性
        anim.toValue = @500;
        // MARK: - 结束后不要闪回去
        anim.removedOnCompletion = NO;
        anim.fillMode = kCAFillModeForwards;
        // MARK: - 通过代理方法,修正按钮的位置!
        anim.delegate = self;
        // 3.添加
        // key 就是一个标记! 键值对的意思! -> 可以在移除的时候,精确找到该动画
        // -> 可以直接写nil! 系统会自动生成key,与动画配对!
        [_btn.layer addAnimation:anim forKey:nil];        
        
    KeyPath:
    CGSize
        widht height
    CGRect
        origin origin.x origin.y size size.x size.y 
    Transform
        rotation scale translation .x.y.z
    Position    
        x  y
    

    核心动画的特点

        // 3.1 核心动画特点1
        - 核心动画结束后,会闪回原来的位置!
        - 核心动画的属性 removedOnCompletion = YES!动画结束后,从layer中移除!
        
    // 如果不让其闪回去,有两种解决方式!
        - 方式一
        通过设置两个属性达到目的:
            removedOnCompletion = NO!
            fillMode = kCAFillModeForwards!
        // 但是有个缺点:
        需要用户交互的控件,它的真实位置没有改动!
        核心动画在运动的时候,为了性能考虑,创建了[展现层,只能看的],在运动!真实的位置并没有发生改变!    
        - 方式二
        通过设置核心动画的代理,不需要遵守协议![代理方法是以NSObject的分类形式定义的!]
        实现代理方法
            - 核心动画开始!
            - 核心动画结束!
    

    CAAnimationDelegate

    // 核心动画开始
    - (void)animationDidStart:(CAAnimation *)anim {
    }
    
    // 核心动画结束
    - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    
    在这个方法中:你可以在核心动画结束后,将控件的真实位置挪过来!
    将layer中的动画移除掉
    }
    

    核心动画[基本动画]

    /**
     // 这个对象规定了属性值的在中间进行修改。
     The objects defining the property values being interpolated between.
     
     // 所有的都是可选的,并且最多两个为非nil值。对象的类型应该跟动画属性的的类型向匹配【以CALayer.h文件中描述的原则为准】。支持的动画模式如下:
     * All are optional, and no more than two should be non-nil. The object
     * type should match the type of the property being animated (using the
     * standard rules described in CALayer.h). The supported modes of
     * animation are:
     *
     // fromValue和toValue不是nil,在fromValue和toValue之间进行改变
     * - both `fromValue' and `toValue' non-nil. Interpolates between
     * `fromValue' and `toValue'.
     *
     // fromValue和byValue不是nil,在fromValue和【fromValue加上byValue】之间进行改变
     * - `fromValue' and `byValue' non-nil. Interpolates between
     * `fromValue' and `fromValue' plus `byValue'.
     *
     // byValue和toValue不是nil,在【toValue 减去 byValue】和toVlue之间进行改变
     * - `byValue' and `toValue' non-nil. Interpolates between `toValue'
     * minus `byValue' and `toValue'.
     *
     // fromValue 不为nil,在fromValue和当前显示位置的值之间进行改变
     * - `fromValue' non-nil. Interpolates between `fromValue' and the
     * current presentation value of the property.
     *
     // toValue不是nil,在这个属性当前的值和toValue之间进行改变
     * - `toValue' non-nil. Interpolates between the layer's current value
     * of the property in the render tree and `toValue'.
     *
     // byValue不是nil,在这个属性当前的值和【当前值 加上 byValue】之间进行改变
     * - `byValue' non-nil. Interpolates between the layer's current value
     * of the property in the render tree and that plus `byValue'.
     
     */
     
    // 修改位置
    - (void)demoPosition {
        // 1.创建基本动画
        CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"position"];
        // 2.设置属性
        // 从哪里  到 哪里
        anim.fromValue = [NSValue valueWithCGPoint:CGPointMake(150, 180)];
        anim.toValue = [NSValue valueWithCGPoint:CGPointMake(275, 567)];
        // 动画结束后不要移除
        anim.removedOnCompletion = NO;
        // 向前填充-> 留在最终的位置!
        anim.fillMode = kCAFillModeForwards;
        // 时长! -> 默认0.25s
        anim.duration = 1;
        // 3.添加
        [_redView.layer addAnimation:anim forKey:nil];
    }
    // 旋转
        // 1.创建对象
        CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
        // 2.设置属性
        anim.toValue = @(M_PI * 2);
        anim.duration = 2;
        // 设置重复次数 CGFLOAT_MAX 无穷大
        anim.repeatCount = CGFLOAT_MAX;
        // 设置核心动画,不要移除!
        // 如果程序进入后台,再次进来后,核心动画会被移除!不在动了
        // 可以打开此属性,保证动画继续执行!
        anim.removedOnCompletion = NO;
        // 3.添加给layer
        [_redView.layer addAnimation:anim forKey:nil];
    // 缩放    
        // 1.创建对象
        CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
        // 2.设置缩放
        anim.toValue = @2;
        anim.repeatCount = CGFLOAT_MAX;
        // 3.添加给layer
        [_redView.layer addAnimation:anim forKey:nil];     
    

    核心动画[关键帧动画]第8天

    // 个人理解关键在于keyPath和values或者path
    
    // 抖动的效果
        // 1.创建对象
        CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation.z"];
        // 2.设置抖动的角度
        anim.values = @[@(-M_PI_4 * 0.3), @(M_PI_4 * 0.3), @(-M_PI_4 * 0.3)];
        anim.duration = 0.5;
        anim.repeatCount = CGFLOAT_MAX;
        // 3.添加给layer
        [_redView.layer addAnimation:anim forKey:nil];
        
    // 沿着图形类型路径运动!
        // 1.创建动画
        CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:@"position"];
        // 2.设置路径
        anim.path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(50, 100, 250, 300)].CGPath;
        anim.repeatCount = CGFLOAT_MAX;
        // 沿着路径运动,修改了时长,做完运动会停一下!又接着走!
        anim.duration = 2;
       
        // 计算模式 -> 强制运动,匀速进行,不管路径有多远!
        // 设计计算模式可以避免停顿问题
        // 使用 calculationMode 是控制关键帧动画时间的另一种方法
        // 将其设置为 kCAAnimationPaced,让 Core Animation 向被驱动的对象施加一个恒定速度,不管路径的各个线段有多长
        anim.calculationMode = kCAAnimationPaced;
       
        // 旋转模式 -> 沿着路径,自行旋转 转的时候需要沿着路径的切线!进行转动!
        anim.rotationMode = kCAAnimationRotateAuto;
        // 3.添加
        [_redView.layer addAnimation:anim forKey:nil];
        
    // 沿着你指定的点的路径运动
        // 1.创建关键帧动画
        CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:@"position"];
        // 2.设置属性
        anim.values = @[
                        [NSValue valueWithCGPoint:CGPointMake(self.view.center.x - 150, 100)],
                        [NSValue valueWithCGPoint:self.view.center],
                        [NSValue valueWithCGPoint:CGPointMake(self.view.center.x + 150, 100)],
                        [NSValue valueWithCGPoint:CGPointMake(self.view.center.x - 150, 100)],
                        ];
        // 时间
        anim.duration = 2;
        // 3.添加给layer!
        [_redView.layer addAnimation:anim forKey:nil];    
    

    ShakeCell [摇一摇]

    思路:
    1. 先让collectionView停止滚动
    2. 获取可见的cell
    3. 给这些可见的cell,给每一个Cell添加抖动的动画
    4. 创建关键帧动画 KeyPath transform.rotation.z
    5. 设置关键值values
    6. 停止抖动.让collectionView可以滚动,每一个Cell移除layer动画
    

    核心动画[组动画]

    就是将动画放到一个数组中,让组动画来统一添加.所有的动画会同时执行.可以设置统一的时间等属性.
    将所有的动画都进行各自添加也可以实现同样的效果.[怎么方便怎么来]   
        // 1.大小 -> 基本动画修改大小
        CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
        anim.toValue = @2;
        
        // 2.旋转 -> 基本动画旋转
        CABasicAnimation *anim2 = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
        anim2.toValue = @(M_PI * 2);
        
        // 3.路径 -> 关键帧动画
        CAKeyframeAnimation *anim3 = [CAKeyframeAnimation animationWithKeyPath:@"position"];
        anim3.path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(100, 100, 250, 400)].CGPath;
        anim3.calculationMode = kCAAnimationPaced;
            
        组动画
        // 1.创建对象
        CAAnimationGroup *group = [CAAnimationGroup animation];
        // 2.设置属性
        group.animations = @[anim, anim2, anim3];
        group.duration = 2;
        group.repeatCount = CGFLOAT_MAX;
        // 3.添加给layer
        [_redView.layer addAnimation:group forKey:nil];
    

    核心动画[转场动画]

    转场动画
        // 1.创建动画对象
        CATransition *anim = [CATransition animation];
        // 设置动画的形式,和方向!
        anim.type = kCATransitionFade;
        anim.subtype = kCATransitionFromBottom;
        // 3.添加给layer
        [view.layer addAnimation:anim forKey:nil];
        
    通过UIView的类方法,也可以实现转场效果
        //    参数1 要执行动画的视图
        //    参数2 动画的时长
        //    参数3 动画的选项
        //    参数4 动画的内容-> 执行动画的代码
        //    参数5 动画结束后需要执行的操作
        [UIView transitionWithView:view duration:0.5 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
            // 需要执行动画的代码
            ((UIImageView *)recognizer.view).image = [UIImage imageNamed:imgName];
        } completion:nil];    
    

    modal控制器

        // modal -> 显示控制器 present!
        // dimiss -> 销毁控制器! dismiss!    
        // 原则
        谁modal,谁就负责dismiss!
        
    

    alloc init

    创建一个对象 alloc init 相当于下面这个方法中的两个参数为nil
    - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {}
    

    自定义转场动画Dismiss

    // 第一种利用手势加动画实现
    1. 首先控制器是present出来的
    2. modal的样式
    下面的方法或者在 init 方法中设置style
    
    - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
        if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
            //显示的 style 如果不设置自定义Modal的背景是黑色的
            self.modalPresentationStyle = UIModalPresentationCustom;
        }
        return self;
    }
    3. 给控制器添加一个拖拽手势
    4. 获取拖拽的偏移量
    5. 根据偏移的距离计算角度
    6. 根据不同状态执行不同操作
    7. 修改控制器的layer的position和anchorPoint
    
    switch (recognizer.state) {
    // 开始的时候修改锚点和position
            case UIGestureRecognizerStateBegan:
                self.view.layer.position = CGPointMake(size.width * 0.5, size.height * 1.5);
                self.view.layer.anchorPoint = CGPointMake(0.5, 1.5);
    
    // 改变的时候进行旋转        
            case UIGestureRecognizerStateChanged:
                self.view.transform = CGAffineTransformMakeRotation(angle);
                break;
    
    // 结束的时候判断角度的大小,来判断是否消失        
            case UIGestureRecognizerStateEnded:
                if (ABS(angle) > 0.30) {
                    [self dismissViewControllerAnimated:YES completion:nil];
                    break;
                }
    
    // 失败的话就返回原来的位置,并将锚点和position还原            
            case UIGestureRecognizerStateFailed:
            case UIGestureRecognizerStateCancelled:
            // 大括号的作用,限制其中代码块的作用域!
            {
                [UIView animateWithDuration:0.25 animations:^{
                    self.view.transform = CGAffineTransformIdentity;
                } completion:^(BOOL finished) {
                    
                    self.view.layer.anchorPoint = CGPointMake(0.5, 0.5);
                    self.view.layer.position = CGPointMake(size.width * 0.5, size.height * 0.5);
                }];
            }
                break;
            default:
                break;
        }
        
    

    自定义转场动画modal

    1. 给控制器指定VC.transitioningDelegate = 对象
    2. 对象遵守UIViewControllerTransitioningDelegate协议
    3. 实现方法
        // 谁负责实现动画的效果
        - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
        // presented  先要present出来的控制器!
        // presenting 想要进行present动作的控制器!
        // source     从哪里显示!源控制器!  presenting 和 source 大部分时候都是同一个对象!
            return self;
        }
    4. 负责实现动画效果的对象要遵守UIViewControllerAnimatedTransitioning协议
    5. 实现方法
    // 动画时长
        - (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {
            return 0.5;
        }    
    // 动画效果
    - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
            // 1.获取将要显示的控制器
        UIViewController *toVc = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
        // 2.获取容器视图
        UIView *containerView = [transitionContext containerView];
        toVc.view.bounds = containerView.bounds;
        // 3.添加视图
        [containerView addSubview:toVc.view];
        // 4.旋转
        toVc.view.transform = CGAffineTransformMakeRotation(-M_PI_2);
        // 5.修改初始点
        CGSize size = toVc.view.bounds.size;
        toVc.view.layer.anchorPoint = CGPointMake(0.5, 1.5);
        toVc.view.layer.position = CGPointMake(size.width * 0.5, size.height * 1.5);
        // 6.动画旋转出来
        [UIView animateWithDuration:0.5 animations:^{
            toVc.view.transform = CGAffineTransformIdentity;
        } completion:^(BOOL finished) {
            toVc.view.layer.anchorPoint = CGPointMake(0.5, 0.5);
            toVc.view.layer.position = CGPointMake(size.width * 0.5, size.height * 0.5);
            [transitionContext completeTransition:YES];
        }];
        动画结束后,一定要告诉系统,执行完毕! 否则系统会一直等待动画结束![等待的过程中用户交互被关闭了]
        // [transitionContext completeTransition:YES];
    }
    

    PCH 文件介绍

        #ifndef PrefixHeader_pch
        #define PrefixHeader_pch
    
        // 1. 需要做屏蔽,如果不是oc的内容,就不要导入oc的头文件
        #ifdef __OBJC__ // 屏蔽不是OC的文件!
        2. 头文件
        #import "项目中需要的头文件.h"
    
        3. // MARK: - 打印调试的宏!
        #ifdef DEBUG // 测试环境!
        #define NSLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __FUNCTION__, __LINE__, ##__VA_ARGS__)
        #else // 其他环境
        #define NSLog(...) // 打印信息也是非常耗性能!
    
        #endif
        #endif
        #endif
    
    作用:
        - 存放公用的宏或者头文件!
        - 每次程序在运行的时候,都会先把pch文件中的东西编译一遍!
        
        // pch文件在Xcode6以前,每次新建项目,都会默认带着一个pch头文件! 项目名称-PrefixHead.pch!
        // Xcode 6以后,苹果就关掉了!创建的项目不再带着这个文件!
        // 原因:速度太慢了!
        // 但是公司里面都是会用这个文件的!        
    
    配置: 新建PCH文件的时候记得勾选项目的target
    在build Settings 的Prefix Header 中添加文件的路径
    根路径/项目名称/文件名称    
    

    JSON

    一、JOSN 简介
        JSON 本质上,就是一个"特殊格式"的字符串
    
        - JSON 是网络上用来传输数据使用最广泛的数据格式,没有之一
        - JSON 出身草根,是 Javascript 的子集,专门负责描述数据格式
        - Javascript 是做网页开发使用的一种"脚本"语言
        - Javascript & Java 没有任何关系!
    
        参考网站:// http://www.w3cschool.cc
    
    二、JSON 语法规则
    
        JSON 的语法格式和 OC 中的快速包装字典和数组的语法几乎一样!
    
        - 数据以 key/value 值对表示
        - 数据由逗号分隔
        - 花括号保存对象
        - 方括号保存数组
    
    三、JSON 值
    
        - 数字(整数或浮点数)
        - 字符串(在双引号中)
        - 逻辑值(true 或 false)
        - 数组(在方括号中)
        - 对象(在花括号中)
        - null
    
    四、序列化 & 反序列化
    
        - 序列化:在向服务器发送数据之前,将 NSArray / NSDictionary 转换成二进制的过程
        - 反序列化:在从服务器接收到数据之后,将二进制数据转换成 NSArray / NSDictionary 的过程
    
    五、NSJSONSerialization 类
    
        - 专门负责在 JSON 和 Foundation 对象直接转换的类
        - 可以转换成 JSON 的 Foundation 对象需要具备以下条件:
        - 顶级节点是 NSArray 或者 NSDictionary
        - 所有的对象是 NSString, NSNumber, NSArray, NSDictionary 或者 NSNull
        - 所有字典的 key 是 NSString
        - NSNumber 不是空或者无穷大福
    

    JSON解析

    1. 先通过URL或者网络请求什么的获取JOSN数据
    2. 将JSON数据转换成对应的字典或者数组类型
    3. 字典转模型,开始使用
    

    相关文章

      网友评论

          本文标题:iOS 无规律总结[UI进阶篇]

          本文链接:https://www.haomeiwen.com/subject/gbaibttx.html