UI-1

---恢复内容开始---

1 [TOC] # 一-开发之前

1.学习大纲

-w1000
技术目标:

  • 强化编程思想:抽取封装能力,思考解决能力,框架设计能力
  • 强化自我能力:解决问题能力,自学能力,知识存储记录能力

2.项目启动过程

运行就是打包的过程,把包放到模拟器中,此时设置一系列的内容,找到程序的入口(MainInterface),依次按照步骤来.

3.拖拽的控件

  • 为什么用weak?
    拖拽的控件所在的控制器都是被强引用所存在的,这个控制器被强引用,控制器的view被控制器强引用,view的控件被view强引用。所以外部拖线这件事其实就是我拿到这个控件,而不需要strong再引用了。

  • 什么可以单击事件?
    一般继承UIControl的都有单击事件

  • 常见错误

    • 属性连线出问题了(找不到属性或者属性多了):this class is not key value coding-compliant for the key xxxxxx
    • 方法出问题了(找不到方法了): unrecoginzed selector sent to instance

二-UI

UIView

  1. 概念
    屏幕上所有的UI元素都叫做控件,所有控件都继承自UIView,哪怕是UIControl也是继承UIView.这些控件的基本属性都放到了公共的UIView中.

  2. 常见属性/方法

    • superView: 父控件
    • subViews: 子控件
    • tag: 标识
    • frame: 控件在父控件的位置尺寸(以父控件左上角为00)
    • bounds : 控件的位置尺寸(以自己左上角为00)
      • 所以一般只改尺寸,xy都是0 (一般layer层用)
    • center : 控件中心(以父控件左上角为00)
    • addSubView: 添加
    • removeFromsuperView: 从父控件移除
    • viewWithTag:根据tag找到View
      • 尽量少使用tag,因为效率差,很乱
  3. ViewDidLoad:

    • 系统调用
    • 控制器的View加载完毕调用
  4. 常用控件:

UILabel

用于显示文字

  1. 属性
    • numberofLines: 文字行数,等于0的时候自动换行
    • lineBreakMode: 换行模式,是头部省略还是尾部省略还是中间省略
    • font: 字体

UIImageView

用于显示图片

  1. 属性

    • contentMode: 填充模式
      带有scale的会缩放,不带的不会缩放,但是需要裁剪
      

    typedef NS_ENUM(NSInteger, UIViewContentMode) {
    UIViewContentModeScaleToFill,// 完全压缩或拉伸
    UIViewContentModeScaleAspectFit, // 宽高比不变,不会变形
    UIViewContentModeScaleAspectFill,// 宽高比不变,填充
    UIViewContentModeRedraw,// 重新绘制
    UIViewContentModeCenter,
    UIViewContentModeTop,
    UIViewContentModeBottom,
    UIViewContentModeLeft,
    UIViewContentModeRight,
    UIViewContentModeTopLeft,
    UIViewContentModeTopRight,
    UIViewContentModeBottomLeft,
    UIViewContentModeBottomRight,
    };

    
    - `clipsToBounds`: 裁剪多余部分
    - `animationImages`: 设置动画图片(放一个数组)
    - `animationRepeatCount`: 播放次数
    - `animationDuration`: 播放时间
    
    
  2. 设置毛玻璃效果
    由于UIToolBar自带毛玻璃效果,所以可以给UIImageView增加UIToolBar的方法设置毛玻璃的简单效果。UIToolBar有一个barStyle枚举可以设置毛玻璃样式,配合alpha透明度可以做设置。

  3. 序列帧动画

  • 加载图片的两种方式:
    1. UIImageNamed
      • 就算指向他的指针被销毁,该资源也不会从内存销毁
      • 放到Assets里的蹄片,只能imageNmaed加载,默认就有缓存的
      • 所以是图片经常使用的时候,就把图片放Assets里,用imageNamed方法
    2. imageWithContentsOfFile
      • 指向他的指针被销毁,资源就从内存中小时

      • 无法缓存,用于不经常使用的大批量的图片

      • 从资源路径中找(SandBox)

      • 手机里的缓存数据都是在沙盒中

      • 图片放到Assets里就拿不到路径了,放到项目中可以。具体可以在查看沙盒的时候找Bundle包,里面有的图片资源就是项目中的,没有的就是放到Assets里面的

      • 拿到图片路径:

      NSString *path = [[NSBundle mainBundle] pathForResource:@"资源名字" ofType:@"资源类型"];
      self.imgView.image = [UIImage imageWithContentsOfFile:path];
      ```
  • 关于颜色:
    • 白色的RGB都是255
    • 黑色的RGB都是0
    • 灰色的RGB都一样,靠近0就是深灰,靠近255就是浅灰
    • 还有什么hex格式,argb格式等

UIButton

按钮既能显示文字又能显示图片,还能随时调整内部图片文字的位置

  1. 状态

    • normal:一般
    • hightlighted:高亮
    • disabled:不可点击
  2. 内容图片和背景图片

    • 内容图片不会随按钮变大而变化
    • 背景图片会随着按钮变化而拉伸
  3. 属性

    • 文字,图片等设置都需要用set方法,因为要区分状态
    • 设置按钮字体:button.titleLabel.font
    • 获得按钮的文字,文字颜色,图片,背景图片 :xxxForState
  4. button不同于imageView和label,它是用addTarget监听的,但是其实都可以通过手势添加监听。

  5. 调整按钮内部子控件位置:默认的是图片左边,文字右边,但是开发是各种需求的

    当自己算完按钮每部距离什么时候,可能出现文字显示不全问题,这时候只需要sizeToFit,但是有时会出现文字错位问题!因为这个方法可能会修改center,那么可以先sizetofit算,然后再修改center即可。

    方法1: UIButton有两个方法返回CGRect,可以自定义Button重写这两个方法

    - (CGRect)titleRectForContentRect:(CGRect)contentRect;
    
  • (CGRect)imageRectForContentRect:(CGRect)contentRect;
    方法2: 其实既然都重写了,不如直接在`layoutSubViews`方法里面修改
    方法3: 不需要重写一个新的Button,直接修改内边距
    ```objc{
    CGFloat labelWidth = modelButton.titleLabel.frame.size.width;  
    

CGFloat imageWith = modelButton.imageView.frame.size.width;  
modelButton.imageEdgeInsets = UIEdgeInsetsMake(0, labelWidth, 0, -labelWidth);  
modelButton.titleEdgeInsets = UIEdgeInsetsMake(0, -imageWith, 0, imageWith);

```

```objc
/*

一、按钮的状态
1.UIControlStateNormal
1> 除开UIControlStateHighlighted、UIControlStateDisabled、UIControlStateSelected以外的其他情况,都是normal状态
2> 这种状态下的按钮【可以】接收点击事件

2.UIControlStateHighlighted
1> 【当按住按钮不松开】或者【highlighted = YES】时就能达到这种状态
2> 这种状态下的按钮【可以】接收点击事件

3.UIControlStateDisabled
1> 【button.enabled = NO】时就能达到这种状态
2> 这种状态下的按钮【无法】接收点击事件

4.UIControlStateSelected
1> 【button.selected = YES】时就能达到这种状态
2> 这种状态下的按钮【可以】接收点击事件

二、让按钮无法点击的2种方法
1> button.enabled = NO;
*【会】进入UIControlStateDisabled状态

2> button.userInteractionEnabled = NO;
*【不会】进入UIControlStateDisabled状态,继续保持当前状态

*/

```
  1. 图片拉伸
    在一些聊天软件中,那些聊天气泡,美工都是准备一张图的,这时候这个Button的背景就是图片,但是Button很大的时候拉伸很难看。此时需要代码进行拉伸。这种图片一般都是要保护一部分不被拉伸,例如四个角什么的

    • 代码拉伸
    // 拉伸
    UIImage *img = [UIImage imageNamed:@"1"];
    // 方法一:返回一张受保护并且拉伸平铺的图片(edge是受保护区域),此时就留下了图片最中心1,1的像素点无线平铺
    UIImage *resizableImage = [img resizableImageWithCapInsets:UIEdgeInsetsMake(img.size.height * 0.5, img.size.width * 0.5, img.size.height * 0.5 - 1, img.size.width * 0.5 - 1)];
    // 方法二:左边和右边需要保护的区域
    UIImage *strechImage = [img stretchableImageWithLeftCapWidth:img.size.width * 0.5 topCapHeight:img.size.height * 0.5]    
    [self.btn setBackgroundImage:resizableImage forState:UIControlStateNormal];
    

    其实这些可以写在分类里,返回一张受保护的图片(写一个UIImage分类)

    • 直接拉伸
      选择Slicing,会自动计算最合适的保护区域,要不自己拽。

懒加载

访问数据就是重写get方法,一般的控件,数组等都应该用懒加载

plist

开发中有一些小数据可以存储在plist中。例如很多页面用的数据,NSArray或者NSDictionary之类的数据。

  1. 创建
    • 生成plist只有可能添加的是字典或者数组。一般是直接在plist中添加,或者代码写好字典数组,然后writeToFile到本地,然后拽到项目中
  2. 解析
    // 写入
    NSArray *array = @[@"1",@"2",@{@"aaa":@"aa"}];
    [array writeToFile:@"/Users/aixiaoxin/Desktop/study.plist" atomically:YES];
    
    // 读取
    
    NSArray *arr = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"study" ofType:@"plist"]];
    NSLog(@"%@",arr);
    
    

字典转模型

所谓模型,就是专门用来存放数据的对象。

方法:

  1. 纯手工:自己创造模型,模型新增构造方法initWithDict等,然后外界赋值,内部实现.
    -(instancetype)initWithDict:(NSDictionary *)dict {
    
    if (self == [super init]) {
       
       self.name = dict[@"name"];
       
       self.icon = dict[@"icon"];
    }
    return self;
    

}
```

  1. kvc字典转模型:

  2. YYModel:

自定义控件

  • 代码创建

自定义UIView,一般在init初始化方法中添加自己的子控件(initWithFrame中写就可以,这个方法init也会调用),在layoutsubView中布局frame(这里能拿到self.frame),再引入模型接口,在模型的set方法中赋值模型,设置数据(或者提供接口方法直接设置)

  • xib创建
  1. xib:是轻量级的,用来描述局部的UI界面 .xib打包后就是.nib。所以加载是通过nib加载的。
  2. 加载xibView:

// 读取xib
// last是最后一个数组,因为一个xib可能拽了很多view,其实一般情况下就一个,便于开发
UIView *xibView = [[[NSBundle mainBundle] loadNibNamed:@"Play" owner:nil options:nil] lastObject];
xibView.frame = CGRectMake(100, 100, 300, 100);
[self.view addSubview:xibView];

```
  1. xib的绑定和连线
    和代码类似,关联类,然后传入模型等。
  2. 注意
    • 如果xib各处都用,那应该提供快速的创建方法

      // 快速加载xib
      

+(instancetype)play {
return [[[NSBundle mainBundle] loadNibNamed:@"Play" owner:nil options:nil] lastObject];
}

  • 如果xib的.m中,还想用代码创建子控件,此时和纯代码是不一样的,纯代码是init和initWithFrame方法。xib是initWithCoder:,在这里创建子控件就可以了。
  • 如果子控件是从xib中创建的initWithCoder中创建的,此时控件是处于为唤醒状态,此时要用到awakeFromNib方法,在这里可以添加xib中创建的子控件的子控件。其实如果想在xib中再加控件,统一这个方法就行。
  • xib创建的是不可以用allocinit直接加载的。
  1. 加载原理
    xib转化成xml代码,里面有控件所有的设置。

UIView渐变动画

方法

KVC

  1. KVC 可以取值/赋值/字典转模型等。

    • 赋值:setValueForKey:可以进行自动类型转换,如果用KVC和网络搭建,非常方便。一般一个对象KVC赋值属性值是可行的。而 setValueForKeyPath: keyPath是可以嵌套对象的,假如赋值一个人的狗属性的名字(点语法点出来),就可以 person setValue: @"旺财 forKeyPath:@"dog.name"。其实后者包含前者全部功能,所以直接用setValueForKeyPath就行。

    • 改变类的私有变量: 一般私有类都是在.m的{}中,此时是点不出来的,这意味着我定义了一个_age,而我直接可以setValueForKeyPath:@"_age"来修改这个值(写@"age"都行,他会去找下划线和不带下划线的)。

    • 字典转模型:

    -(instancetype)initWithDict:(NSDictionary *)dict {
    
    if (self == [super init]) {
        
        // 废弃
    

// self.name = dict[@"name"];
// self.icon = dict[@"icon"];

    // KVC字典转模型
    [self setValuesForKeysWithDictionary:dict];
}
return self;

}
-(void)setValue:(id)value forUndefinedKey:(NSString *)key {
}```
KVC字典转模型的问题:如果有模型嵌套,就会出问题,因为是暴力的键值对赋值。当然,此时就会用到框架(YYModel,MJExtension)

  • 取值:valueForKeyPath方法,开发一般不怎么用,因为点语法就行了
  • 运行时的时候KVC非常强大

KVO

Key Value Observing(键值监听)当某个对象的属性发生改变时进行监听。假如监听一个对象的名字改变,scrollView的contentOffSet改变等。
如果一个类用KVO监听,苹果会自动为这个类生成一个子类(查看isa指针的时候就是NSKVONotifying_XXX类型),所以用的多了性能很差


监听属性,就要给属性的对象增加观察者

    MYModel *model = [MYModel new];
    // 增加监听观察后,那么应该有个方法来进行 监听完成后的作为
    [model addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
    model.name = @"改变前";
    model.name = @"改变后";
    [model removeObserver:self forKeyPath:@"name"];
/**
 监听属性值改变后

 @param keyPath 要改变的属性
 @param object 要改变的属性所属的对象
 @param change 改变的内容
 @param context 上下文
 */
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    
    NSLog(@"%@==%@===%@",keyPath,object,change);
    
}

结果:

2017-03-20 18:10:22.341 button[6755:1544348] name==<MYModel: 0x60800022d7e0>==={
    kind = 1;
    new = "\U6539\U53d8\U524d";
}
2017-03-20 18:13:10.575 button[6785:1563494] name==<MYModel: 0x618000030500>==={
    kind = 1;
    new = "\U6539\U53d8\U540e";
}

UIScrollView

设备的屏幕大小是有限的,普通的View不具备滚动功能,UIScrollView可以通过滚动展示大量内容。


  1. 基本使用

    • 将需要展示的内容添加到UIScrollViez中
    • 如果想要滚动,设置contentSize属性,也就是滚动范围(可以远大于本体scrollView的size),所以当contentSize尺寸减去本体size是负数的话就无法滚动了,怎么也要大于本体尺寸
    • 默认scroll就设置了clipsToBounds = YES,也就是内容超出边框后就隐藏
    • 默认scroll设置了弹簧效果
    • 无法滚动?
      • 没有设置contentSize
      • scrollView.scrollEnabled设置为NO(只是不能滚动)
      • scrollView.userInteraction设置为NO(失去任何交互能力)
      • scrollView.subViews自带两个滚动条
      • 滚动就是修改contentOffset,此时可以用set方法加一个动画
  2. 常见属性

    • bounces默认YES,弹簧效果
    • alwaysBouncesHorizontal默认NO,和下一个方法一样,设置YES的时候可以让没有contentsize的scrollView滚动(只是一个弹簧效果)
    • alwaysBouncesVertical默认NO,这两个值就算设置YES也没区别,因为这时候已经设置了contentSize。当没有设置contentSize的时候,如果设置这两个是YES就可以滚动了,就像网络图片还没下载完成,只有下拉才能下载,然后加载contentsize,这时候就需要用到这两个属性了
    • showHorizontalScrollIndicator展示水平滚动条
    • showVerticalScrollIndicator展示垂直滚动条
        public var contentOffset: CGPoint // 滚动视图偏移量
        public var contentSize: CGSize // 滚动视图的内容大小
        public var contentInset: UIEdgeInsets // scrollview的contentview的顶点相对于scrollview的位置
        
        public var directionalLockEnabled: Bool // default NO. if YES, try to lock vertical or horizontal scrolling while dragging
        public var bounces: Bool // 是否开启回弹效果
        public var alwaysBounceVertical: Bool // 是否始终水平回弹
        public var alwaysBounceHorizontal: Bool // 是否始终垂直回弹
        public var pagingEnabled: Bool // 是否分页(开启后滑动有自动定位功能)
        public var scrollEnabled: Bool // 是否可以滚动
        public var showsHorizontalScrollIndicator: Bool // 是否显示水平滚动条
        public var showsVerticalScrollIndicator: Bool // 是否显示垂直滚动条
        public var indicatorStyle: UIScrollViewIndicatorStyle // 滚动条的样式(黑色/白色/默认) 
        public var scrollsToTop: Bool // 默认YES,单击上方状态栏(服务商和电池那个地方),会自动回到顶端  
    
    
    
  3. 重要属性

    • contentOffSet,是一个CGPoint属性,是一个偏移量
      • 可以控制内容滚动的位置
      • 可以得知内容滚动的位置(get)
    • contentInset,是UIEdgeInset属性,是内边距,分别是上左下右,滚动的时候会多出来一块儿不显示的部分就是内边距效果,会变相的增加额外的滚动距离
  4. 三者区别

  5. 代理(监听行为)

在UIScrollView滚动时候就会告诉代理,代理发送一系列消息。

 public protocol UIScrollViewDelegate : NSObjectProtocol {
@available(iOS 2.0, *)
optional public func scrollViewDidScroll(scrollView: UIScrollView) // 滚动的时候
@available(iOS 3.2, *)
optional public func scrollViewDidZoom(scrollView: UIScrollView) // 滚动视图已经缩放时候
@available(iOS 2.0, *)
optional public func scrollViewWillBeginDragging(scrollView: UIScrollView) // 将要开始拖动视图调用
@available(iOS 5.0, *)
optional public func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) // 将要结束拖拽视图调用
@available(iOS 2.0, *)
optional public func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) // 已经结束拖拽视图
@available(iOS 2.0, *)
optional public func scrollViewWillBeginDecelerating(scrollView: UIScrollView) // 即将减速的时候
@available(iOS 2.0, *)
optional public func scrollViewDidEndDecelerating(scrollView: UIScrollView) // 减速停止的时候
@available(iOS 2.0, *)
optional public func scrollViewDidEndScrollingAnimation(scrollView: UIScrollView) // 滚动动画结束时候
@available(iOS 2.0, *)
optional public func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? // 设置进行缩放的子视图(谁缩放)
@available(iOS 3.2, *)
optional public func scrollViewWillBeginZooming(scrollView: UIScrollView, withView view: UIView?) // 将要进行缩放时候
@available(iOS 2.0, *)
optional public func scrollViewDidEndZooming(scrollView: UIScrollView, withView view: UIView?, atScale scale: CGFloat) // 完成缩放的时候
@available(iOS 2.0, *)
optional public func scrollViewShouldScrollToTop(scrollView: UIScrollView) -> Bool // return a yes if you want to scroll to the top. if not defined, assumes YES
@available(iOS 2.0, *)
optional public func scrollViewDidScrollToTop(scrollView: UIScrollView) // 点击状态栏回到顶部,仅针对可上下滚动的scrollview 有效}

注意: 任何OC对象都可以成为代理,哪怕你SCrollView滚动,让一条狗遵守ScrollViewDelegate并且实现方法成为代理,那再狗的.m中就可以监听到scrollView滚动

6.代理(监听行为)

scrollView可以通过手势完成内容缩放放大
1. 用代理找到要缩放的scrollViewviewForZoomingInScrollView
2. 设置缩放比例:

```
    scrollView.maximumZoomScale = 2.0
    scrollView.minimumZoomScale = 0.5
    scrollView.bouncesZoom = true // 缩放属性是否回弹 
```

7.分页功能
- 就像苹果手机开机的分页页面,当你滚动的时候,左边比较多右边比较少,那么就自动滚动到左边,这就是分页功能。很多App的分页滚动功能,也是依据分页来实现的。
其实就是一个属性pageingEnable开启后就可以了。系统分页的判断是按照scrollView的大小来分的。如果要添加分页控件再自行添加UIpageControl
- 很多分页的点都用得图片,这样看起来很好看,但是其实观察UIpageControl头文件是没有图片选择的,它隐藏在.m的私有拓展中有一个属性_currentPageImage,_otherPageImage,此时可以通过KVC来转化成自己喜欢的图片
- 分页定时器:每隔一定的时间,希望scrollView做一些事情,去滚动,要用到定时器NSTimer。在用户即将拖拽scrollView的时候停止定时器,不在拖拽的时候开启定时器。

1.一个定时器要不永久开启,只要一停止就自动销毁了,只能创建一个新的
2.程序一启动会默认开启一条线程,是主线程,主线程显示刷新UI界面,处理用户交互界面,所以主页面在进行别的操作的时候,定时器可能会卡住。所以要把`NSTimer`添加到运行循环(修改定时器在runloop的模式,目的是不管主线程在做什么,都会分配一定时间处理事情)。
// 添加到运行循环
[[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

常见控件监听

随便一个控件,点开头文件,如果继承自UIControl就都可以通过addTarget来监听,有些特殊的提供delegate就可以通过代理来监听。像TextField这类比较特殊,它既有代理也继承UIControl,此时两者都可以监听开始编辑,文字改变,结束编辑等状态的。

屏幕适配

  1. 程序员只需关注的是点,而不是像素,一个点所容纳的像素越多,越清晰。在iphone4S时代,屏幕尺寸固定,那么固定值算就可以适配。在5和5s时代尺寸变化了,此时还无法用autolayout(因为还要适配ios5),此时用的是AutoresizingMask,在iphone6时代开始用AutoLayOut

  2. Autoresizing

    • Autoresizing和AutoLayout是不兼容的,想要使用现在需要去掉AutoLayout。我只在xib中用过,就是六根线。外面四根线是和父亲的关系,里面两根线是子类的关系(选择了会跟随父控件伸缩)
    • 代码实现就是有个AutoResizingMask枚举,可以选择是上左下右怎么适配
    • 只能解决子控件和父控件之间的关系
    • AutoLayout比Autoresizing强得多
  3. AutoLayout

    • ios6 开始引用,不过真正从xcode5开始大量使用,也是现在的主流

    • 可以解决任何控件之间的关系

    • 用自动布局,就不要在设置frame

    • 利用约束参照完成布局

    • 左对齐/右对齐/顶部对齐/底部对齐

    • 中心点x和父控件一样/中心点y和父控件一样

    • 两个控件中心点x一样/中心点y一样

    • 在做pad留言板的时候,因为用的是autoResizing,对于UILabel总是文字居中显示,这时候上下端会出现很多空缺,解决办法只能计算文字到底多高,在固定高度,而用了AutoLayout就可以完美解决这个问题。UILabel只需要给一个位置就不报错,尺寸会自动根据文字内容而计算宽高,此时应该设置下最大宽度(不是真实宽度)Relation->LessThan,告诉UILabel什么时候开始换行。那么固定位置和宽度,UILabel就会自动剔除上下两端的空格了。

    • 让父控件的高度跟随子控件伸缩:例如很多表格类型的app,每个cell高度都不同,因为里面的子控件大相径庭。 其实这时候cell 就不能直接设置高度了,而是要设置自己底部和会影响到你的子类的关系,例如你的子类最后一个控件的底永远和父类的底差10个点,那么不管子类怎么变高,父容器也会跟着变。

  4. 约束优先级:两个互相冲突的约束如果修改优先级,那么优先级较高的生效,当优先级较高的失效了或者删除了,优先级较低的才会生效。例如三个方块并排放各自距离20,当中间的删除后,让第三个自动到中间位置,这时候就用到优先级了。

代码实现AutoLayout

  1. 禁止autoresizingself.myView.translatesAutoresizingMaskIntoConstraints = NO;

  2. 创建NSLayoutconstraint类创建约束关系

    • 宽高什么的添加到自己上
    • 和父控件之间控制位置的约束添加到父控件上
    • 两个平等的控件(都是儿子)之间的约束添加到父控件上
  3. VFL可视化语言

  4. 修改约束:

        // 修改约束
    self.myWidth.constant = 25;
    [UIView animateWithDuration:2.0 animations:^{
        // 需要强制更新
        [self.view layoutIfNeeded];
    }];
    

Masonry

- 目前最流行的第三方框架
- [Masonry/SnipKit](https://github.com/SnapKit/Masonry)
- 两个pch上面的宏定义可以让`mas_`全部省略
- `makeConstraints`是添加新的约束
- `updateConstraints`是更新约束
- `remakeConstraints`是删除之前所有约束,然后添加新的约束

UITableView

基本

  1. TableView分为PlainGroup两种style类型,这个是只读的,意味着只有在创建tableView的时候可以设置。
  2. indexPath分为sectionrow两个属性,分别是组和行,根据这个可以确定唯一的一行
  3. cell.accessoryView可以设置系统cell右侧的那个按钮状态样式
  4. UITableView内部自动封装了一套复用机制。会让空闲的cell进入可重用线程池,当有新的cell出现会先去线程池中找有没有可复用的,没有才会创建。假如有100组数据,需要100个cell,但是手机上每屏只能放下10个,其实这时候只需创建11个cell就够用了。每一个数据模型就是一个cell。通过数据源方法来对每个cell进行数据设置。通过代理方法设置关于tableView的头,尾等视图设置

UITableView常见属性

  1. rowHeight:每一行cell高度
  2. sectionHeader(Footer)Height:每一组头尾高度
  3. separateStyle:分割线样式(删除分割线separateNone)
  4. tableHeader(footer)View:整个tableView的头尾部,上面的是每一组的头尾,这个是整个的(插播个广告什么的)

Cell常见属性

  1. accessoryView--accessoryType:都是设置右边控件的(View优先级更大)
  2. selectedStyle:选中样式(有Blur,Gray,None等,ios6之前有效果,7之后全部都是灰色)
  3. backgroundView:cell背景可以是好看的图片,selectedBackgroundView:选中的背景图片
  4. contentView:cell上添加的东西都是添加在contentView上的。因为有时候cell删除,滑动,整个cell都向左移动了,此时如果移动cell非常麻烦,但是移动contentView就方便多了
  5. 自带cell中的imageView,textLabel等属性都是延迟加载的,是懒加载的,用到才会加载

数据源方法

  • 提供几组,几行,每一个cell什么样子,数据是什么

代理方法

  • 提供cell单机事件
  • 提供cell的头/尾视图设置,头/尾高度设置,设置了这些,前面属性设置的高度就失效了。
  • 提供了每一行的高度,rowHeight是有局限性的,这个可以让每个cell高度不同

UITableViewController

  • 每一个控制器都有View,在UITableViewController中:self.viewself.tableView是一个对象。
  • 当数据比较少,还想去掉没有数据的分割线时:可以设置self.tableView.tableFooterView = [UIView new]

性能优化

  • cell标准写法.注册方法

自定义等高的cell

  • 无论是系统自带cell还是自定义创建注册的cell,这个[cell class]都是通过cell alloc initWithStyle方法实现的
-(instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    
    
    if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
        
        _name = [UILabel new];
         [self.contentView addSubview:_name];
        
    }
    return self;
    
    
}
  • 至于cell内部自然要用约束来控制。当需要用frame计算的时候一般会写在layoutifNeed里面,因为这里才能拿到self的frame.但是用约束计算的话直接写在cell alloc initWithStyle方法里就行(写在addSubView以后),因为这是根据父控件的约束,不管父控件如何
  • xib创建cell时候注册nib
[self.tableView registerNib:[UINib nibWithNibName:NSStringFromClass([MyTableViewCell class]) bundle:nil] forCellReuseIdentifier:@"cell"];
  • 一个tableView显示不同的cell:由于cellforRowAtIndexpath方法返回一个cell,所以可以根据indexpath改变cell。说白了就是注册N个cell和reusedId,根据indexPath来dequeue不同的cell。广告cell就这么来的
  • 自定义分割线:添加高度为1的UIView

静态cell和动态cell

  • 动态cell是数据.数量.高度等等都是动态获取的
  • 静态cellStatic Cells是写死的,在sb中/xib中直接修改cell类型然后绘制出来的。可以做一些设置界面,然后通过代理监听
  • 静态动态只是创建区别,功能都是实现cell
  • 静态cell 也是可以自定义的,也可以画画然后关联类,然后从waakefromNib读取关联的cell

字典转模型

  • 字典转模型无非就是遍历数组,然后拿到数组里的每一个字典,然后给模型用KVC赋值,这是一个重复的过程,所以要省事,用插件。而且有的模型很复杂,嵌套模型很多,这么写起来更麻烦。

  • MJExtension
    GitHubMJExtension

    model-objetArrayWith...

  • YYModel
    GitHubYYModel

    model-yy_modelWithJson..


自定义不等高的cell

  • (计算不等高UILabel)如果frame计算,需要注意UILabel内容不一样的时候,要想计算它的高度,需要
    
    UILabel *lab = [[UILabel alloc] init];
    // 100是固定宽度 后者是最大宽度,不限制
    CGSize size = CGSizeMake(100, MAXFLOAT);
    // 计算出高度
    CGFloat height = [lab.text sizeWithFont:[UIFont systemFontOfSize:14] constrainedToSize:size].height;
  • 一些app都是frame计算的,假如需求是frame来计算不等高度的cell。控制器在heightForRowAtIndexpath方法中可以拿到模型方便计算,但是在这里计算要调用无数次,性能很差,这时候还必须先返回一个高度给cell,所以一开始觉得应该给数据模型增加所有的cell子控件frame 和总高度frame属性,在总高度属性中,利用懒加载来计算 高度/各个frame,然后外界heightForRowAtIndexpath直接设置高度。但是这么做,重用刷新就会出现问题,因为刷新的时候高度大于0,但是不是你想要的高度。真正做法是刷新的时候,拿到json的时候,字典转模型的时候,创建一个新的frame模型,里面存放着高度和各个cell子控件的frame,每次刷新数据就绑定好,然后heightForRowAtIndexpath直接拿frame模型。
  • 打印各种方法执行顺序,假如10条数据在页面,其实是先执行10次heightForRowAtIndexpath才会执行cellForRowAtIndexPath,说白了就是cell展示前,所有高度早就计算好了。
  • masonry每次都会重新计算,在一些很复杂很复杂的cell控件中,其实frame算性能比较好。(一哥们亲身经历)
  • cell内部计算的时候也不一定写在layoutsubVies,写在模型的set方法也可以,因为这时候是肯定有frame的
  • self.tableView.estimatedRowHeight:预估高度,会优化性能,让heightForRowAtIndexpath执行次数少很多。而且没有估算的时候,是先执行heightForRowAtIndexpath后执行cellForRowAtIndexPath。当写了估算行高后,反过来了,因为已经有一个默认高度了。

数据刷新

  • 全局刷新:reloadData:就是重新调用数据源方法
  • 局部刷新:reloadRowsAtIndexPath:适合模型数量不变
  • 添加刷新: insertRowsAtIndexPath
  • 删除刷新:deleteRowsAtIndexPath

左滑删除

  • 实现代理方法就可以出现滑动删除
-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{
    // 删除模型,动画单行刷新
    [self.dataSource removeObjectAtIndex:indexPath.row];
    [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationTop];   
}
-(NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath{
    return @"删除";
}
  • 左滑出现多个按钮(例如微信,滑动出现删除/关注),此时只需要一个新方法,如果新增了这个方法,上面的return 删除监听就实效了。
-(NSArray<UITableViewRowAction *> *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath{
    
    UITableViewRowAction *action1 = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault title:@"关注" handler:^(UITableViewRowAction * _Nonnull action, NSIndexPath * _Nonnull indexPath) {
        
        // 关注

    }];
    action1.backgroundColor = [UIColor orangeColor];
    
    UITableViewRowAction *action2 = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault title:@"删除" handler:^(UITableViewRowAction * _Nonnull action, NSIndexPath * _Nonnull indexPath) {
        
        
        // 删除模型,动画单行刷新
        [self.dataSource removeObjectAtIndex:indexPath.row];
        
        [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationTop];
        
    }];
    action2.backgroundColor = [UIColor redColor];
    
    return @[action1,action2];
}

如果想点击后退出编辑模式,tableView.editing = no

  • 编辑模式tableView.editing = yes时,左侧就会出现删除按钮,其实就是上方的那个编辑模式。而且编辑模式有三种
// 设置cell的编辑模式,有NONE,插入,删除三种
    override func tableView(tableView: UITableView, editingStyleForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCellEditingStyle {
        
        return UITableViewCellEditingStyle.Insert
        
    }
  • 批量删除
    有俩属性,编辑模式下就可以批量勾选等等

UICollectionView

  • 流水布局UICollectionViewFlowLayout:说白了就是当里面内容变宽了变窄了这个那个了,但是整个布局会改变。就像手机界面,删除一个app,后面的会滑上去。

  • self.collrctionView 不等于 self.view,不像tableView,collectionView是添加到view上的。

  • flowLayout布局的时候可以设置item的大小,行间距,item间距,滚动方向,每一组内边距(sectionInset),

  • self.collectionView可以设置分页(pagingEnable),弹簧效果(bounces),隐藏滚动条(showH..)等操作,其实很多都是UIScrollView的属性

  • 相册照片浏览器

// 必须要有布局,自带循环机制,每一个item就是每一个cell
// 必须要注册cell,并且必须要自定义cell,因为collectionView的默认cell什么都没有!
// 调整尺寸什么的都是布局的事,cell自定义后通过代理来完成注册,数量,模型内容
// 当要做的内容例如item大小不一的时候,系统无法满足,此时要自定义流水布局

#import "FlowLayout.h"
/*
    自定义布局:只要了解5个方法
 
 - (void)prepareLayout;
 
 - (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect;
 
 - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds;
 
 - (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity; // return a point at which to rest after scrolling - for layouts that want snap-to-point scrolling behavior

 - (CGSize)collectionViewContentSize;

 */
@implementation FlowLayout

/*
 UICollectionViewLayoutAttributes:确定cell的尺寸
 一个UICollectionViewLayoutAttributes对象就对应一个cell
 拿到UICollectionViewLayoutAttributes相当于拿到cell
 */


// 重写它方法,扩展功能

// 什么时候调用:collectionView第一次布局,collectionView刷新的时候也会调用
// 作用:计算cell的布局,条件:cell的位置是固定不变
// - (void)prepareLayout
//{
//    [super prepareLayout];
//    
//    NSLog(@"%s",__func__);
//    
//}


// 作用:指定一段区域给你这段区域内cell的尺寸(就是这个rect,一开始系统固定的,之后超过这个值就新增)
// 可以一次性返回所有cell尺寸,也可以每隔一个距离返回cell
- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
{
//    NSLog(@"%s",__func__);
// 这时候就返回所有了
    NSArray *attrs = [super layoutAttributesForElementsInRect:CGRectMake(0, 0, MAXFLOAT, MAXFLOAT)];
    
    return attrs;
    
}

// 什么时候调用:用户手指一松开就会调用
// 作用:确定最终偏移量
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity{

    // 拖动比较快 最终偏移量 不等于 手指离开时偏移量
    
    // 最终偏移量
    CGPoint targetP = [super targetContentOffsetForProposedContentOffset:proposedContentOffset withScrollingVelocity:velocity];
    
    // 获取collectionView偏移量
    NSLog(@"%@ %@",NSStringFromCGPoint(targetP),NSStringFromCGPoint(self.collectionView.contentOffset));
    
    
    return CGPointZero;
}

// Invalidate:刷新
// 在滚动的时候是否允许刷新布局
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
    return YES;
}

// 计算collectionView滚动范围
//- (CGSize)collectionViewContentSize{
//    return [super collectionViewContentSize];
//}


MVC

  • M是模型,V是视图,C是控制器。View显示什么取决于M,控制器负责把数据模型给View。MVC因为模型的存在解决cell循环利用表格出bug了。只需要记住,不要随意直接修改cell上的值,要改,就改模型。

通知

  • 每一个应用程序都有一个NSNotificationCenter实例,负责不同对象之间消息传递。
  • 我做一件事要让外界知道,我就发布通知(post),谁来监听谁就(post),这个过程是一对多的,我广播发布,谁注册了监听就把通知给谁。
  • NSNotification一个通知有三个属性
    1. name:通知名字
    2. object:通知发布者(id)
    3. userInfo:传递内容信息(字典)
    // 发布通知(object是谁发送)
    [[NSNotificationCenter defaultCenter] postNotificationName:@"RemoveNotification" object:self userInfo:@{@"money":self.wine.money}];
    // 监听通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(remove:) name:@"RemoveNotification" object:nil];

- (void)remove:(NSNotification*)noti {
    
    NSLog(@"%@---%@---%@",noti.name,noti.object,noti.userInfo);
    int money = [[noti.userInfo objectForKey:@"money"] intValue];
    self.totalPriceLabel.text = [NSString stringWithFormat:@"%d",[self.totalPriceLabel.text intValue]-money];
    
}

object谁发布的name什么通知。可以不写name,那就是监听这个object的所有通知。

  • 移除通知remove,当对象被销毁,就删除
  • 通知是有顺序的,要先监听add,再发出post,否则兼听不到
  • add通知的时候还有一个方法会让填写一个队列和block,队列不需要的时候写nil,这个也是可以用的。当队列需要,填写的队列就决定block在哪里执行。而且这个通知的移除不是一般的removeself!因为是系统观察的,方法你都没写!
@property (nonatomic, weak) id observe;
@end

@implementation ViewController
- (void)test
{
    // 方式一:
    // Observer:观察者
    // selector:只要一监听到通知,就会调用观察者这个方法
    // Name:通知名称
    // object:谁发出的通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reciveNote) name:@"note" object:nil];

}

- (void)test2
{
    // Name:通知名称
    // object:谁发出的通知
    // queue:决定block在哪个线程执行,nil:在发布通知的线程中执行
    // [NSOperationQueue mainQueue]:一般都是使用主队列
    // usingBlock:只要监听到通知,就会执行这个block
    // 注意:一定要记得移除
    _observe = [[NSNotificationCenter defaultCenter] addObserverForName:@"note" object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
        
        // 只要监听到通知 就会调用
        NSLog(@"%@",[NSThread currentThread]);
        
        NSLog(@"%@",self);
        
    }];
}


// 一个对象即将销毁就会调用
- (void)dealloc
{
    // 移除通知
    [[NSNotificationCenter defaultCenter] removeObserver:_observe];
}
// 异步:监听通知 主线程:发出通知 接收通知代码在主线程
// 主线程:监听通知 异步:发出通知 接收通知代码在异步
// 注意:在接收通知代码中 可以加上主队列任务


UIDevice通知

  • UIDevice提供一个单例UIDevice-currentDevice,代表设备,可以获取一些设备信息,例如电池电量,电池状态,设备类型,设备系统等等。
  • 以前的代码会判断系统版本sytemVersion来进行代码不同的写法,可以用于系统适配
  • UIDevice会不间断的发布一些通知,例如
    1. 设备旋转
    2. 电池状态改变
    3. 电池电量
    4. 近距离传感器(例如接电话后屏幕变黑)

键盘通知

  • 键盘也有一些系统的通知,来判断键盘状态

    -w500

UI进阶(多控制器就是进阶)

基础

启动页LaunchScreen

  • 可以用自带的sb设置,也可以用Assset里面的启动页设置。
  • sb设置的底层实现:把LauchScreen里的内容生成了一张图片储存在沙盒中

info.plist

  • 作用:设置应用程序配置信息。最外层节点是一个字典
  • Bundle name:应用程序名称(项目名称一般不是中文的,因为要打包,但是app很多中文名字,这时候在这里改这个选项是中文,例如 财务审批
  • Bundle identifier:应用程序唯一标识(上传appStore的时候必须有这个,做推送的时候也必须有这个(推送是把消息发送给苹果服务器,服务器根据你手机的Bundle identifier来找到你这个app))
  • Bundle version string , short:版本号,软件版本号
  • Bundle version:应用程序打包版本号

PCH

  • 作用:
    • 自定义Log
    // 定义debugFKLog
    

ifdef DEBUG

define FKLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), PRETTY_FUNCTION, LINE, ##VA_ARGS);

else

define FKLLog(...)

endif

```

- 存储公共宏
- 存储公共头文件(自定义的分类等)
  • 从xcode6开始苹果不推荐PCH,就是不编译了。现在需要创建pch后,在Build Settings - 搜索prefix - 寻找 LLVM 7.0 Lauguage - pch改YES - 填写pch路径fullPath
  • 原理:app编译时候,把pch所有内容拷贝到所有文件中(如果是混合开发,有c文件的话就会报错了,因为c不识别pch里面你的UI宏,这时候还给在pch做设置)

UIApplication

  • UIApplication是应用程序的象征。每一个app只有一个,是单例。sharedApplication
  • 一个ios程序启动后创建的第一个对象就是UIApplication,利用它而已做一些应用层设置。例如
    • applicationIconBadgeNumber:设置app右上角红色提醒数字
  1. 设置图标

  2. 注册通知

  3. 设置图标属性

/**
取得用户授权
*/

  • (void)badgeNotification:(UIApplication *)application {
    // 取得用户授权显示通知[BadgeNumberUIApplication层]
    id typeBadge = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeBadge categories:nil];
    id typeSound = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeSound categories:nil];
    id typeAlert = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert categories:nil];
    [application registerUserNotificationSettings:typeBadge];
    [application registerUserNotificationSettings:typeSound];
    [application registerUserNotificationSettings:typeAlert];
    }
    // ios 8后需要授权,目前只会写一种授权方式,iOS10有新方法
    NSInteger times = arc4random() % 100;
    [[UIApplication sharedApplication] setApplicationIconBadgeNumber:times];
- `networkActivituIndicatorVisible`:设置联网小菊花的可见性(Bool)一般写在网络方法中
- `statusBarHidden`:设置状态栏隐藏

```

/**
想要使用 默认要在info.plist新增最后一个键值对,选no,这时候控制器设置无效
全局设置状态栏白色
全局设置导航栏背景mainColor
全局设置导航栏标题文字颜色白色,微软雅黑
全局设置导航栏Item颜色 白色
*/

  • (void)systemSetting {

    [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
    [[UINavigationBar appearance] setBarTintColor:mainColor];
    [[UINavigationBar appearance] setTintColor:[UIColor whiteColor]];
    [[UINavigationBar appearance] setTitleTextAttributes: [NSDictionary dictionaryWithObjectsAndKeys: [UIColor whiteColor], NSForegroundColorAttributeName, [UIFont fontWithName:@"MicrosoftYaHei" size:18.0], NSFontAttributeName, nil]];
    }

    
    

// 不是全局 交给每个控制器独立控制,默认是交给控制器的
-(UIStatusBarStyle)preferredStatusBarStyle {

return UIStatusBarStyleLightContent;

}
-(BOOL)prefersStatusBarHidden {

return YES;
}
```
- `keyWindow`:应用程序主窗口



- 强大的`openURL`方法 `[NSURL URLWithString:]`-->`根据协议头tel: sms:http:等来区分`
	- 可以打电话
	- 发短信
	- 发邮件
	- 打开网络资源
	- 打开其他app
  • UIApplication代理:
    app容易受到干扰,打电话这个那个会切出去。当这些系统干扰出现的时候,app需要做一些事。例如保存数据等。此时UIApplication会有代理监听(也就是AppDelegate里面那一堆方法)
    • 系统来电等
    • 程序启动关闭等
    • 收到内存警告

应用程序启动原理

  1. 执行main函数->执行UIApplicationMain->创建UIApplication对象->执行代理
  2. 开启运行循环(主运行循环),保证app不退出(死循环)
  3. 加载info.plist(一些基本属性,然后看看有没有main.sb,有就加载,没有就创建)
  4. 启动完毕,进入appDelegate代理

UIWindow

UIWindow是一个特殊的UIView,ios启动完毕后,启动的第一个视图控件就是UIWindow,然后创建控制器View,把控制器view加到UIWindow上。所以说没有UIWindow就看不到任何UI界面的。

  • 纯代码写app的时候window是要手动创建的
  • self.window makeKeyAndVisible的作用是显示,把跟控制器的view加载到我的window上。
  • 其实键盘,状态栏也是一个UIWindow
  • 从ios9后,如果添加了多个窗口,例如又创建了UIWindow,makeKey了,状态栏就没了,消失了,解决办法就是让状态栏给应用程序管理。
  • windowLevel:窗口层级(不同窗口谁前谁后可以分层级)Alert > StatusBar > Normal

控制器如何加载View?

  1. 控制器利用loadView方法,专门创建控制器View。当控制器的View第一次使用的时候调用此方法。那么loadView做了什么?

     1.  判断如果控制器是sb加载的,就找sb的View设置为自己当前View
     2.  如果是xib加载,就把xibView设置自己View
     3.  如果都不是,就会创建一个空白的View
    

    一旦重写了这个方法,那么这个View就需要自己创建了,这样可以自定义控制器最一开始的View。

  2. 控制器View都是懒加载的

  3. 如果一个控件是透明的,那么它不能接收事件。一开始创建的控制器不是透明的,只是颜色是透明的,是可以接收事件的。

UITexiField

  1. self.inputView:修改文本框弹出键盘类型

UIPickerView/UIDatePicker

PickerView

代理选择设置内容。一般配合UITextField的键盘类型设置为是自定义的PickerView

DatePicker

datepickerMode: 日期模式可以选择想要的年月日
locale:设置区域为localeWithLocaleId:@"zh"中国
监听日期改变用addTarget-Valuechange

项目中这些选项都有,例如左边国家右边国旗的界面, 年月日时间的界面, 选择城市(市区)的界面。国家国旗就是一个pickerView,自定义View弄上imageView和label,用代理完成。年月日就如上,选择城市~~

String

字符串 用copy目的是外界修改了,不会影响到自己。其实,一个字符串属性被赋值的时候,self.name = @"xxx"这个赋值的值,要不就是写死的,要不就是网络获取的,但是一定都是NSString类型。而copy属性呢,会在set方法的时候做一次copy,copy会做一次判断,判断外界给的值得类型是可变的还是不可变的,如果是不可变的,直接赋值,如果是可变的,就copy一份赋值。所以其实app的字符串都是不可变的,所以写strong也是可以的,甚至会少一次判断,性能上会高一些。财务审批NSString非常多,这一点用strong是可行的。

---恢复内容结束---

![](http://images2015.cnblogs.com/blog/1014815/201707/1014815-20170705105216878-1240811650.jpg)
posted @ 2017-07-05 10:53  三更小新  阅读(378)  评论(0编辑  收藏  举报