当前位置: 首页 > news >正文

[iOS]-UIKit

目录:

  • 参考的博客:
  • CALayer和UIView
    • 区别
    • 关系
  • tableview
    • tableView遵循的两个delegate
    • tableView复用机制
    • tableView自适应高度
      • AutoLayout自适应高度
      • 手动计算高度,进行存储
        • sizeToFit
        • boundingRectWithSize
    • 什么时候用tableview相比于scrollerView,tableview做了哪些别的操作
      • 给一个UIScrollerViw实现UITableView
  • collectionview
  • UIViewController的生命周期
  • frame、bounds
    • setBounds
    • Inset
  • UIViewController和UIResponder
  • 离屏渲染

参考的博客:

[iOS开发]UIKit
iOS离屏渲染

CALayer和UIView

区别

UIView继承自UIResponder,主要负责事件传递、事件响应,属于基于UIKit框架
CALayer继承自NSObject,负责图像渲染,动画和视图的显示,属于QuartzCore框架

而且这两大内容都符合单一职责原则,单一职责通俗地讲就是一个类只做一件事。

虽然CALayer没有事件响应的能力,但是我们可以通过

  • hitTest
  • convert
    两个方法来判断事件是不是在layer上,从而来给事件添加点击事件

关系

  • 所有的界面元素都继承自UIView。它真正的绘图部分是由CALayer的类来管理的。UIView本身更像是一个CALayer的管理器,访问其跟绘图和坐标有关的属性,例如framebounds等等,实质上内部都是在访问它所包含的CALayer的相关属性
  • UIView有个layer属性,可以返回它的主CALayer实例。UIView有一个layerClass方法,返回主layer所使用的类(默认返回就是[CALayer class]),UIView的子类可以通过重载这个方法来时UIView使用不同的CALayer来显示
  • UIViewCALayer类似于UIView的子view树状结构,也可以向它的layer上添加子layer,来完成某些特殊的表示
    CALayer *grayCover = [[CALayer alloc] init];
    grayCover.backgroundColor = [[UIColor greenColor] CGColor];
    grayCover.position = CGPointMake(200,200);
    grayCover.bounds = CGRectMake(0,0,80,80);
    [self.view.layer addSublayer:grayCover];

可以看到会在目标view上对应位置将白色改为了绿色
请添加图片描述
并没有添加新图层,而是在原本view上改变颜色,这和addsubview并不一样。

  • CALayer视图结构类似UIView的子view树形结构,可以向它的layer上添加子layer,类似于向View上添加View,来完成某些特殊的表示,但是不同之处就是CALayer添加的时候并不会添加新图层,类似于修改原来的layer
  • UIVIewlayer树形在系统内部,被系统维护三份copy
    • 第一份,逻辑树,代码可以在里面操作,例如通过代码更改layer的属性(比如frame\bounds)就在这一份进行操作
    • 第二份,动画树,这是一个中间层,系统在这一层更改属性,进行各种渲染操作
    • 第三份,显示树,这棵树的内容就是当前正被显示在屏幕上的内容

这三棵树的逻辑结构都是一样的,区别只是有各自的属性

tableview

tableView遵循的两个delegate

tableView对于iOS开发者来说是无人不知无人不晓

下面我们来复习一下tableView需要完成的操作

  • 第一步就是正常的创建tableViewtableView需要遵循两个delegate<UITableViewDelegate, UITableViewDataSource>,顾名思义,dataSource意思为数据源,delegate意为代理,其内部包含很多方法
    • UITableView需要一个数据源(dataSource)来显示数据,UITableView会向数据源查询一共有多少行数据以及每一行显示什么数据等等。没有设置数据源的UITableView只是个空壳。凡是遵守UITableViewDataSource协议的OC对象,都可以是UITableView的数据源
    • 我们也需要为UITableView设置代理对象(delegate),以便在UITableView触发某些事件时做出相应的处理,比如选中了某一行。凡是遵守了UITableViewDelegate协议的OC对象。都可以是UITableView的代理对象,一般会让控制器充当UITableViewdataSourcedelegate,通过我们手动实现协议中的某些方法来完成tableView的实现

具体到方法:

这三个方法都是dataSource协议中的方法,我们往往通过这三个方法实现数据的显示:

//控制行数的
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
//控制组数的
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 
//cell的编辑函数
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 

这两个常用的方法是delegate中的方法,提供某些交互方法:

//控制行高的
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
//cell的点击事件函数
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath

tableView复用机制

iOS-tableViewCell的重用机制
iOS UITableviewCell的重用机制(这位大佬对于cell重用理解的非常到位)

我自己在实验中对cell的重用总结如下:

非自定义cell

  1. 第一次加载创建tableView的时候,是屏幕上最多可以显示几行cell就先创建几个cell,此时复用池里什么都没有
  2. 开始下滑tableView,刚开始滑动时由于第一行没有完全滑出屏幕进复用池时下面新cell已经显示到屏幕上了,所以下方这个新划出来的cell就是新创建的cell而不是复用的cell(复用时也得看新加载的cell和复用池中的备用cell有没有同类型的id,没有就不复用),然后接着滑动第一个cell就完全出屏幕进入复用池了,下面我们接着滑动,接着那个需要新显示出来的cell如果和复用池中我们保存的第一个cell是同种id,如果是就直接拿来复用,如果不是就新创建(判断是否能复用和新创建的操作都在cell的编辑函数:cellForRowAtIndexPath:中)。
  3. 上滑tableView和下滑原理基本一致,完全出屏幕的入复用池,需要新加载到屏幕的cell的先看池中有没有同idcell,有了就复用,没有就创建。

自定义cell

  1. 第一次加载创建tableView的时候,是屏幕上最多可以显示几行cell就先创建几个cell,此时复用池里什么都没有
  2. 开始下滑tableView,刚开始滑动时由于第一行没有完全滑出屏幕进复用池时下面新cell已经显示到屏幕上了,所以下方这个新划出来的cell就是新创建的cell而不是复用的cell(复用时也得看新加载的cell和复用池中的备用cell有没有同类型的id,没有就不复用),然后接着滑动第一个cell就完全出屏幕进入复用池了,下面我们接着滑动,接着那个需要新显示出来的cell如果和复用池中我们保存的第一个cell是同种id,如果是就直接拿来复用,如果不是就新创建(判断是否能复用和新创建的操作都在cell的编辑函数:cellForRowAtIndexPath:中)。
  3. 上滑tableView和下滑原理基本一致,完全出屏幕的入复用池,需要新加载到屏幕的cell的先看池中有没有同idcell,有了就复用,没有就创建。

其实自定义和非自定义的cell的复用情况都是一样的。
下面我们讲一个使用cell时需要注意的点:

  • 复用时从复用池中取出来的cell可以是已经捆绑过数据或者加过子视图的,所以,如果有必要,要清除数据,比如labeltext)和removeadd过的子视图(使用tag),否则就有可能造成复用后出现显示的内容不符预期的情况,比如说我们有一个开始创建id为@"test"的cell,我们在执行cell的编辑函数:cellForRowAtIndexPath:中有一个判断分支,符合某种情况时需要在cell上显示一个button,而不符合那种情况的时候就不显示button,而恰好我们这个cell符合情况,于是就添加了button相关的数据,然后显示出了button,接着我们滑动了tableView,导致我们刚才创建的这个id@"test"cell进入了复用池,然后再继续下滑的时候有一个新的id@"test"cell需要被加载出来,于是就查看了复用池,发现里面有同idcell,所以就直接拿来复用了,复用的时候对里面的数据进行了自己的设定,本类好像没什么问题,但是恰好这个新cell不符合那个要显示button的条件,然后就不会去执行对button添加数据的操作,我们预想着这个新cell和之前我们创建的id@"test"cell的区别就是新的没有button,旧的有button,然后两者其他控件也就是数据内容不同(比如labeltext不一样这些),但是实际情况却是这个新创建的cell上面显示的依然存在button,和我们预想的不一致,其原因就是复用时所取出来的旧cell是已经捆绑过数据且加过子视图的,虽然我们新cell创建时没有走对button添加数据的代码,但是由于旧cell走过了,且向button添加的数据都在,所以就导致新cell上面就有一个和旧cell一模一样的button这就是复用中可能存在的一个常见的难搞的问题,但是解决起来其实也非常简单,我们只需要在新cell创建走编辑函数:cellForRowAtIndexPath:时,在里面加一段操作,去remove多余的那个子视图或者清除旧数据,而且依我本人之见,最好使用清除旧数据而不是remove多余的子视图,因为这个正在新建的cell后面也许也会进入自动释放池,而且它到时候也可能会被拿来复用,如果那个复用它的cell刚好需要显示button而这个被复用的cellbutton这个视图都没添加到cell上,那直接向button添加数据时程序就会crash,所以清除所有数据是不错的选择,反正每次执行编辑函数:cellForRowAtIndexPath:时都会为对应行组的cell重新添加那些子视图上的数据(相当于覆写了旧数据),我们只需要在所有的重新添加数据操作之前讲被复用的cell上子视图的数据全删了就行,但是如果偏要走remove子视图的方法也不是不行,我们可以巧妙点,从复用池取出来要被复用的cell之后直接重新alloc初始化一下这个被复用的cell,相当于之前清空了原本保存的所有子视图的全部数据,而原本添加到cell上的子视图都还在,只是没有数据不显示罢了,这也是很好的手段。

tableView自适应高度

关于自适应高度,主要思路有两个

  • 手动计算高度,进行存储,然后通过heightForRow来调用存在数组中的缓存进行实现。
  • iOS7之后UITableView默认的Self-Sizing技术,不需要实现heightForRow方法,系统通过AutoLayout约束自动计算cell高度

下面我们就进行一下这两个实现思路的讲解
先说第二个吧,我认为这个方法既然是新技术,那么就一定能帮我们更好的使用tableView

AutoLayout自适应高度

主要思路就是两步

  1. 使用AutoLayout方法
  2. 不要实现heightForRowAt方法

如何使用AutoLayout方法?

AutoLayout是一种自动布局技术,可以帮我们自动实现布局,苹果官方也推荐开发者尽量使用AutoLayout来布局UI界面。

其实就是两个核心概念,参考、约束

听起来很像我们的Masonry的使用,确实,使用Masonry来完成自适应高度是很舒服的,直接使用其约束条件,不用我们自己手动实现各种约束条件。

以自定义cell中的Label的自动展开为例

使用自适应高度的代码如下:

- (instancetype) initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
   
    if ([reuseIdentifier isEqualToString:@"11"]) {
        _labelTest = [[UILabel alloc] init];
        _labelTest.numberOfLines = 0;
        [self.contentView addSubview:_labelTest];
        [self layoutIfNeeded];
    } else {
        _labelTest = [[UILabel alloc] init];
        _labelTest.numberOfLines = 0;
        [self.contentView addSubview:_labelTest];
        [self layoutIfNeeded];
    }
    
    return self;
}

- (void) layoutSubviews {
    [super layoutSubviews];
    [self.contentView addSubview:_labelTest];
    [_labelTest mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(_labelTest.superview.mas_top).offset(10.0);
        make.bottom.equalTo(_labelTest.superview.mas_bottom).offset(-10);
        make.left.equalTo(_labelTest.superview.mas_left).offset(10.0);
        make.right.equalTo(_labelTest.superview.mas_right).offset(-10.0);
    }];
}

正常使用一个Masonry应该怎么实现呢?很简单,给topleftsize,这三个条件就足以用最简单的方式实现一个布局操作,而自适应高度的话最后一个最接近底部的控件就得写上其距底部的高度,就是四个边,上左下右给了界限,中间的高度自己根据自己的内容的高度自己实现,达到将cell撑开的效果。

另外,我们在用masonry实现自适应label展开时也得注意一个点,就是labelnumberOfLines需要设置为0,这个就是对label的行数不做限制。

这基本上就是AutoLayout的设置部分,还有一个重点的东西:[self layoutIfNeeded];,这行代码为什么得手动写一遍?其实这涉及到了一个东西:“自动展开的MasonrylayouSubviews不起作用的问题”,这是因为iOS10之前,layoutSubViews方法在一种cell初始化时就会调用两次,而iOS10之后,其只会调用一次,就导致我们需要手动补上那一次,就能够让控件得到正确展开,这时候有人可能要问为什么平时没有注意到?其实是因为每种cell初始化的时候都会调用一次,我们平时项目中一般都是多种类型的cell自定义,每种初始化时调用的layoutSubViews方法相当于把上一种cell所缺的一次补上了,然后自己又缺了一次,但是如果我们最后一种不是需要自适应展开的就没有问题,但如果是需要自适应展开的就会导致它无法展开,也是因为我们平时项目中最后一种cell往往不是要展开的,所以不会注意到这个问题。

有一个小细节就是要实现cell自适应展开时就不要去写heightForRow方法了,否则就不会按照自适应来,关于自适应需要配置的东西各iOS版本区别如下:
请添加图片描述
而现在iOS14以后,包括15、16,这两个配置都不需要写了,因为rowHeight的默认值为UITableViewAutomaticDimension苹果官方文档介绍如下:
请添加图片描述
翻译版如下:
请添加图片描述

可以看到为什么不能自适应时去写heightForRow方法了,里面写的清清楚楚,如果实现了heightForRow方法,tableView视图就会忽视自适应属性的配置。

详细的layoutSubViews方法问题可以详见该博客:iOS10后使用Masonry进行自动布局出现的问题及处理述

大佬对Self-Sizing进行了一些优化,详见该博客:iOS Self-Sizing的一点优化
另外还有一个不用Masonry的版本:iOS之SelfSizing Cell-高度自动计算详细讲解(就是用数组存一下自己手动预设好的layout布局,数组中包含上左下右四个位置信息的设置,然后添加到了NSLayoutConstraint去显示,具体操作非常的秀)

下面展示一些代码实例:
本人上方写的label自适应展开的代码的各种情况如下:
如果写为(我们注释掉最后一种celllayoutIfNeeded方法调用):
注: 该例子的前5cell是一种,后5cell是一种。

- (instancetype) initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
   
    if ([reuseIdentifier isEqualToString:@"11"]) {
        _labelTest = [[UILabel alloc] init];
        _labelTest.numberOfLines = 0;
        [self.contentView addSubview:_labelTest];
        [self layoutIfNeeded];
    } else {
        _labelTest = [[UILabel alloc] init];
        _labelTest.numberOfLines = 0;
        [self.contentView addSubview:_labelTest];
        //[self layoutIfNeeded];
    }
    
    return self;
}

- (void) layoutSubviews {
    [super layoutSubviews];
    [self.contentView addSubview:_labelTest];
    [_labelTest mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(_labelTest.superview.mas_top).offset(10.0);
        make.bottom.equalTo(_labelTest.superview.mas_bottom).offset(-10);
        make.left.equalTo(_labelTest.superview.mas_left).offset(10.0);
        make.right.equalTo(_labelTest.superview.mas_right).offset(-10.0);
    }];    
}

运行结果如下:请添加图片描述
可以看到我们最后一种cell并没有得到自适应展开

然后我们又修改代码为如下(恢复最后一种celllayoutIfNeeded方法调用):

- (instancetype) initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {

    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
   
    if ([reuseIdentifier isEqualToString:@"11"]) {
        _labelTest = [[UILabel alloc] init];
        _labelTest.numberOfLines = 0;
        [self.contentView addSubview:_labelTest];
        [self layoutIfNeeded];
    } else {
        _labelTest = [[UILabel alloc] init];
        _labelTest.numberOfLines = 0;
        [self.contentView addSubview:_labelTest];
        [self layoutIfNeeded];
    }
    
    return self;
}

- (void) layoutSubviews {
    [super layoutSubviews];
    [self.contentView addSubview:_labelTest];
    [_labelTest mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(_labelTest.superview.mas_top).offset(10.0);
        make.bottom.equalTo(_labelTest.superview.mas_bottom).offset(-10);
        make.left.equalTo(_labelTest.superview.mas_left).offset(10.0);
        make.right.equalTo(_labelTest.superview.mas_right).offset(-10.0);
    }];    
}

运行效果如下:
请添加图片描述
可以看到我们的最后一种cell也是成功自适应展开了。

接着我们又来修改一种操作(去掉layoutSubviews方法中[super layoutSubviews]调用父类同名方法):

- (instancetype) initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {

    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
   
    if ([reuseIdentifier isEqualToString:@"11"]) {
        _labelTest = [[UILabel alloc] init];
        _labelTest.numberOfLines = 0;
        [self.contentView addSubview:_labelTest];
        [self layoutIfNeeded];
    } else {
        _labelTest = [[UILabel alloc] init];
        _labelTest.numberOfLines = 0;
        [self.contentView addSubview:_labelTest];
        [self layoutIfNeeded];
    }
    
    return self;
}

- (void) layoutSubviews {
	[super layoutSubviews];
    [_labelTest mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(_labelTest.superview.mas_top).offset(10.0);
        make.bottom.equalTo(_labelTest.superview.mas_bottom).offset(-10);
        make.left.equalTo(_labelTest.superview.mas_left).offset(10.0);
        make.right.equalTo(_labelTest.superview.mas_right).offset(-10.0);
    }];    
}

运行结果如下:
请添加图片描述
我们看到最后一种cell确实是展开了,但是仔细看和上一种情况还是有一些区别的,该区别就是本次的每行cell背后的背景色都是透明的,而上次的cell是有默认的白色背景和分割线效果的,这也许就是layoutSubviews方法原来默认的一些实现,不过都是小细节上的处理。

最后我们再展示一种(将label的布局不写在layoutSubviews方法里,而是直接写在cell的编辑函数cellForRowAtIndexPath:里,而且我们去掉了每种cell初始化时调用的[self layoutIfNeeded]):

//cell自定义的两个函数
- (instancetype) initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
   
    if ([reuseIdentifier isEqualToString:@"11"]) {
        _labelTest = [[UILabel alloc] init];
        _labelTest.numberOfLines = 0;
        [self.contentView addSubview:_labelTest];
        //[self layoutIfNeeded];
    } else {
        _labelTest = [[UILabel alloc] init];
        _labelTest.numberOfLines = 0;
        [self.contentView addSubview:_labelTest];
        //[self layoutIfNeeded];
    }
    
    return self;
}

- (void) layoutSubviews {
    [super layoutSubviews];
}


//cell的编辑函数
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    if (indexPath.row < 5) {
        TestCell *cell = [_tableViewTest dequeueReusableCellWithIdentifier:@"11" forIndexPath:indexPath];

        if (cell == nil) {
            cell = [[TestCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"11"];
        }
        
        cell.labelTest.text = @"111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
        
    [cell.labelTest mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(cell.labelTest.superview.mas_top).offset(10.0);
        make.bottom.equalTo(cell.labelTest.superview.mas_bottom).offset(-10);
        make.left.equalTo(cell.labelTest.superview.mas_left).offset(10.0);
        make.right.equalTo(cell.labelTest.superview.mas_right).offset(-10.0);
    }];
    
        return cell;
    } else {
        
        TestCell *cell = [_tableViewTest dequeueReusableCellWithIdentifier:@"12" forIndexPath:indexPath];

        if (cell == nil) {
            cell = [[TestCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"12"];
        }
        
        cell.labelTest.text = @"222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222";
        
        [cell.labelTest mas_makeConstraints:^(MASConstraintMaker *make) {
            make.top.equalTo(cell.labelTest.superview.mas_top).offset(10.0);
            make.bottom.equalTo(cell.labelTest.superview.mas_bottom).offset(-10);
            make.left.equalTo(cell.labelTest.superview.mas_left).offset(10.0);
            make.right.equalTo(cell.labelTest.superview.mas_right).offset(-10.0);
        }];
        
        return cell;
    }
}

运行结果如下:
请添加图片描述
可以看到也是可以自适应展开的。

这种情况下如果不去掉[self layoutIfNeeded];这行代码的话,其效果也是一样的,因为layoutSubviews中已经没有关于label布局约束的代码了,调不调没啥影响,主要是看layoutSubviews中有没有写[super layoutSubviews];,写的话cell默认就有白背景和分割线,不写的话就是一个透明背景,别的啥细节修饰都没有。

手动计算高度,进行存储

两种方法:

  • sizeToFit
  • boundingRectWithSize

sizeToFit

    UILabel * label = [[UILabel alloc] initWithFrame:CGRectMake(10,100, 350, 0)];
    label.numberOfLines = 0;
    label.text =@"当前视图的边界和边界大小的变化123123213213213123123123123123213213123213213213123123213";
    NSLog(@"the label bounds : %@",NSStringFromCGRect(label.frame));
    [label sizeToFit];
    NSLog(@"%f",label.frame.size.height);
    [self.view addSubview:label];

这个例子我们通过设置label.numberOfLines = 0;[label sizeToFit]; 就可以保证其可以自动换行且成为合适的位置,在sizeToFit之后我们就可以得到其空间展开应有的高度。

运行效果和打印结果如下:
请添加图片描述
请添加图片描述
可以看到我们实现了label的自适应展开,而且sizeToFit的确成功算出了label应有的高度。

所以我们可以用sizeToFit方法计算出label应有的高度,然后存入数组中,在cell创建时在heightForRow方法中从数组取值加载合适的cell高度即可。

boundingRectWithSize

详情可参考该博客:iOS 计算字符串长度-boundingRectWithSize:

iOS 7.0之前用sizeWithFont:(计算的不是很准确):

CGFloat width1=[(NSString *)obj sizeWithFont:[UIFont systemFontOfSize:16] constrainedToSize:CGSizeMake(1000, FONTHEIGHT)].width;

iOS 7.0之后用 boundingRectWithSize:
返回文本绘制所占据的矩形空间。

CGRect rect=[(NSString *)obj boundingRectWithSize:CGSizeMake(1000, FONTHEIGHT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:16]} context:nil].size.width;

里面的参数如下:

  • obj 是指要计算显示的字符串
  • boundingRectWithSize 表示计算的宽高限制
    计算高度时,需要宽度固定:CGSizeMake(1000, CGFLOAT_MAX)
    这里的1000也可以用已经确定的控件的宽度替代self.label.width
    计算结果表示在宽度最多为1000高度不限时,显示完全字符串需要的高度
    计算宽度时,需要高度固定:CGSizeMake(CGFLOAT_MAX, 200)
    同理200也可以用已知高度替换:self.label.height
    计算结果表示在高度不超过200时,将给定的字符串现实完全需要的宽度
  • options是文本绘制的附加选项,NSStringDrawingUsesLineFragmentOrigin 是默认基线
  • attributes字典格式,限定字符串显示的样式,一般限制字体较多,比如:@{NSFontAttributeName:[UIFont systemFontOfSize:16]}
  • context包括一些信息,例如如何调整字间距以及缩放。最终,该对象包含的信息将用于文本绘制。一般写nil

下面我们展示一个使用的例子:

    UILabel * labelSecond = [[UILabel alloc] init];
    labelSecond.numberOfLines = 0;
    labelSecond.text =@"当前视图的边界和边界大小的变化123123213213213123123123123123213213123213213213123123213";
    labelSecond.font = [UIFont systemFontOfSize:16];
	//用我们预设的width来算label应该显示的size   
    CGRect rectTest = [labelSecond.text boundingRectWithSize:CGSizeMake(350, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:16]} context:nil];
    
    //从刚才算的size中取出heigth
    //加1是怕四舍五入完之后少了一点导致显示的label少了一行
    CGFloat titleHeight = ceilf(rectTest.size.height) + 1;
    
    //用算出来的结果显示label
    labelSecond.frame = CGRectMake(10,200, 350, titleHeight);
    //将label作为子视图添加到父视图
    [self.view addSubview:labelSecond];

运行结果如下:
请添加图片描述
可以看到实现了label的自适应展开,与sizeToFit同理,添加到数组中,在cell创建时调用heightForRow方法中从数组取值加载合适的cell高度即可。

在使用boundingRectWithSize计算label高度时有几个需要注意的点,且该方法计算label高度很容易出问题,做好一下几点就基本不会出问题了:

  • 计算前设置好label的字体大小,不能设置的17,算的16,这样很容易就会失去最后一行甚至多行的显示。
  • 在计算完成之后,我们需要用ceilf方法来取需要的高度或者宽度,但是由于这个过程会发生四舍五入,所以有可能差的小数部分导致最后一行的label无法显示,所以算出来之后给结果加1,就可以避免这个问题。

什么时候用tableview相比于scrollerView,tableview做了哪些别的操作

给一个UIScrollerViw实现UITableView

详情可参考该博客:ScrollView + 自定义cell 组合(实现复用)

collectionview

collectionView的使用详见:
自定义UICollectionViewLayout实现
iOS UICollectionView基础

UIViewController的生命周期

可参考该博客:[iOS]-ViewController生命周期

frame、bounds

frame是相对于父视图自己的位置,bounds是自己的起始位置。其有两个属性,sizeoriginorigin初始都为0(如果设置了setBounds就不是0),setBounds中前两个变量为负数是什么意思?为何(-30,-30)的偏移量,却可以让view向右下角移动呢?

这是因为setBounds的作用是:强制将自己(view1)坐标系的左上角点,改为(-30,-30)。那么view1的原点,自然就向在右下方偏移(30,30)

这里Inset也是同样道理,-50,-50,rect 的坐标(origin)按照(dx,dy) 进行平移,然后将rect的大小(size) 宽度缩小2倍的dx,高度缩小2倍的dy

举个例子:所以inset 值为正,缩小,值为负,放大。

setBounds

    UIView *viewFirst = [[UIView alloc] init];
    viewFirst.frame = CGRectMake(100, 100, 200, 200);
    //对viewFirst设置setBounds
    [viewFirst setBounds:CGRectMake(-30, -30, 200, 200)];
    
    viewFirst.backgroundColor = [UIColor blueColor];
    
    NSLog(@"111=%@", NSStringFromCGRect(viewFirst.frame));
    
    [self.view addSubview:viewFirst];
    
    UIView *viewSecond = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];

    viewSecond.backgroundColor = [UIColor yellowColor];
   
    NSLog(@"111=%@", NSStringFromCGRect(viewSecond.frame));
    
    [viewFirst addSubview:viewSecond];

运行效果如下:
请添加图片描述
可以看到我们通过对viewFirst设置setBounds,使得所为viewFirst子视图的viewSecond的位置发生了偏移。

其实setBounds写为:setBounds:CGRectMake(-30, -30, 200, 200)就是强制让viewFirst坐标系的左上角点改为(-30, -30),原本没有设置setBounds的话是(0, 0),而我们viewSecondframe为:(0 0 200 200); ,所以就需要找到viewFirst上的对应的(0, 0)位置,由于此时的(0, 0)位置已不是viewFirst视图的左上角,而已经是viewFirst视图的左上角那个点向右和向下各平移30的距离的那个点了,所以也就造成了我们看到的viewSecondviewFirst上是往右下平移了的。

打印结果的话发现:
viewFirstframe为:(100 100 200 200);
viewSecondframe为:(0 0 200 200);

Inset

UIView *viewFirst = [[UIView alloc] init];
    viewFirst.frame = CGRectMake(100, 100, 200, 200);
    viewFirst.backgroundColor = [UIColor blueColor];
    
	NSLog(@"111=%@", NSStringFromCGRect(viewFirst.frame));
	
    [self.view addSubview:viewFirst];
    
    //在viewFirst的frame基础上进行了Inset设置
    CGRect rect = CGRectInset(viewFirst.frame , -10, -10);
    UIView *viewSecond = [[UIView alloc] initWithFrame:rect];
    viewSecond.backgroundColor = [UIColor yellowColor];
    
    NSLog(@"111=%@", NSStringFromCGRect(viewSecond.frame));
    
    [self.view addSubview:viewSecond];

打印结果如下:
请添加图片描述
可以看到设置了CGRectInset(viewFirst.frame , -10, -10);之后,黄色的viewSecond的四个边角都扩展了10的长度。

打印结果的话发现:
viewFirstframe为:(100 100 200 200);
viewSecondframe变为了:(90 90 220 220);

UIViewController和UIResponder

我们最熟悉的UIApplicationUIViewUIViewController这几个类是直接继承自UIResponderUIResponder类是专门用来响应用户的操作处理各种事件(UIEvent)的。
UIResponder提供了用户点击按压检测(presses)以及手势检测(motion)的回调方法,分别对应用户开始、移动、结束以及取消,其中只有在程序强制退出或者来电时,取消事件才会调用。

离屏渲染

On-Screen Rendering:当前屏幕渲染,指的是 GPU 的渲染操作是在当前用于显示的屏幕缓冲区中进行
Off-Screen Rendering:离屏渲染,分为 CPU 离屏渲染和 GPU 离屏渲染两种形式。GPU 离屏渲染指的是 GPU 在当前屏幕缓冲区外新开辟一个缓冲区进行渲染操作

为什么会使用离屏渲染:
当使用圆角,阴影,遮罩的时候,图层属性的混合体被指定为在未预合成之前不能直接在屏幕中绘制,所以就需要屏幕外渲染被唤起

GPU离屏渲染的代价很大:
离屏渲染之所以会特别消耗性能,是因为要创建一个屏幕外的缓冲区,然后从当屏缓冲区切换到屏幕外的缓冲区,然后再完成渲染;其中,创建缓冲区切换上下文最消耗性能,而绘制其实不是性能损耗的主要原因。

上下文之间的切换这个过程的消耗会比较昂贵,涉及到 OpenGLpipelinebarrier,而且 offscreen-render 在每一帧都会涉及到,因此处理不当肯定会对性能产生一定的影响。另外由于离屏渲染会增加 GPU 的工作量,可能会导致 CPU+GPU 的处理时间超出 16.7ms,导致掉帧卡顿

详细的离屏渲染相关可以参考该博客:iOS离屏渲染

相关文章:

  • RT1176 LPSPI驱动移植到RT-THREAD
  • JAVA异步执行线程池
  • 效果最大化的所需素材
  • MySQL中的int(11)类型后的括号是什么意思?ZEROFILL属性
  • 树莓派4B(64位)环境搭建
  • AWS CodeCommit中分支保护
  • 免费搭建查题公众号提供题库接口
  • 13---OpenCV:图像进阶操作之①图像直方图②图像金字塔
  • 石头科技: 决胜百亿扫地机器人“价值高地”
  • 趁着中秋节来临之际,学学如何做好团队管理
  • Spring Cloud项目(八)——使用gateway作为服务网关
  • 为什么要注册商标?有什么好处?
  • C/C++后端开发学习路线总结(附带实习学习经历分享)
  • 广西大学口袋开发板之抢答器
  • 【Python零基础入门篇 · 2】:掌握各种运算符和变量、input()输入输出、映射函数map()结合input()和split()函数实现多值输入
  • [ 一起学React系列 -- 8 ] React中的文件上传
  • 〔开发系列〕一次关于小程序开发的深度总结
  • bearychat的java client
  • HashMap剖析之内部结构
  • js学习笔记
  • PAT A1050
  • Vue 重置组件到初始状态
  • Vue官网教程学习过程中值得记录的一些事情
  • 爱情 北京女病人
  • 百度小程序遇到的问题
  • 从零搭建Koa2 Server
  • 从零开始的无人驾驶 1
  • 翻译 | 老司机带你秒懂内存管理 - 第一部(共三部)
  • 看图轻松理解数据结构与算法系列(基于数组的栈)
  • 聊一聊前端的监控
  • 如何在GitHub上创建个人博客
  • 深入浏览器事件循环的本质
  • 学习笔记DL002:AI、机器学习、表示学习、深度学习,第一次大衰退
  • 再谈express与koa的对比
  • 中国人寿如何基于容器搭建金融PaaS云平台
  • MyCAT水平分库
  • ​html.parser --- 简单的 HTML 和 XHTML 解析器​
  • #每天一道面试题# 什么是MySQL的回表查询
  • $.ajax,axios,fetch三种ajax请求的区别
  • (39)STM32——FLASH闪存
  • (BFS)hdoj2377-Bus Pass
  • (C语言)二分查找 超详细
  • (delphi11最新学习资料) Object Pascal 学习笔记---第5章第5节(delphi中的指针)
  • (博弈 sg入门)kiki's game -- hdu -- 2147
  • (二)七种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB
  • (分类)KNN算法- 参数调优
  • (四)JPA - JQPL 实现增删改查
  • (中等) HDU 4370 0 or 1,建模+Dijkstra。
  • .[hudsonL@cock.li].mkp勒索加密数据库完美恢复---惜分飞
  • .bat批处理(四):路径相关%cd%和%~dp0的区别
  • .htaccess 强制https 单独排除某个目录
  • .NET Micro Framework 4.2 beta 源码探析
  • .net redis定时_一场由fork引发的超时,让我们重新探讨了Redis的抖动问题
  • .NET Standard 的管理策略
  • .net 程序 换成 java,NET程序员如何转行为J2EE之java基础上(9)