17网一起做网店网站,宜兴建设局网站,app定制小程序开发,网站营销外包公司简介UIView和CALayer
UIView
UIView表示屏幕上的一块矩形区域#xff0c;它是基本上iOS中所有可视化控件的父类。UIView可以管理矩形区域里的内容#xff0c;处理矩形区域的事件#xff0c;包括子视图的管理以及动画的实现。
UIKit相关类的继承关系
UIView继承自UIResponde…UIView和CALayer
UIView
UIView表示屏幕上的一块矩形区域它是基本上iOS中所有可视化控件的父类。UIView可以管理矩形区域里的内容处理矩形区域的事件包括子视图的管理以及动画的实现。
UIKit相关类的继承关系
UIView继承自UIResponder所以UIView可以做事件响应它也是所有视图控件直接或间接的父类
CALayer
CALayer继承于NSObject我们称之为层CALayer类的概念与UIView非常类似可以包含图片、文本、背景色等。CALayer直接继承自NSObject没有事件响应的功能。CALayer中包含API可判断某点是否在图层范围内但是没有响应链的存在
UIView和CALayer的关系
在每一个UIView实例当中都有一个默认的支持图层layerUIView负责创建并且管理这个图层。UIView因为里面有layer层才具有显示的功能。UIView仅仅是对layer的一层封装实现了CALayer的delegate提供了处理事件交互的具体功能还有动画底层方法的高级API。可以说CALayer是UIView的内部实现细节。
UIView和CALayer的区别
1UIView可以响应事件CALayer不可以响应事件 2一个 Layer 的 frame 是由它的 anchorPoint,position,bounds,和 transform 共同决定的而一个 View 的 frame 只是简单的返回 Layer的 frame 3UIView主要对显示的内容的管理而CALayer主要侧重显示内容的绘制 4在做 iOS 动画的时候修改非 RootLayer的属性譬如位置、背景色等会默认产生隐式动画而修改UIView则不会。
UIView的CALayer类似于UIView的子view树状结构也可以向它的layer上添加子layer,来完成某些特殊的表示 UIView *firstView [[UIView alloc] init];firstView.frame CGRectMake(200, 200, 200, 200);firstView.backgroundColor [UIColor redColor];[self.view addSubview:firstView];CALayer *layer [[CALayer alloc] init];layer.backgroundColor [[UIColor greenColor] CGColor];layer.position CGPointMake(100,100); //中心点layer.bounds CGRectMake(100,100,80,80);[firstView.layer addSublayer:layer];可以看出并没有在父视图添加新的图层而是在view上改变了颜色这和addSubView并不一样。可以看出layer是对View显示内容的绘制。
CAlayer视图结构类似于UIView的子View树形结构可以在layer上添加子layer类似于view添加view来实现一些特殊的表示。不同之处CALayer在添加的时候不会添加新视图类似于修改原来的layer。UIVIew的layer树形在系统内部被系统维护三份copy 第一份逻辑树代码可以在里面操作例如通过代码更改layer的属性比如frame\bounds就在这一份进行操作第二份动画树这是一个中间层系统在这一层更改属性进行各种渲染操作第三份显示树这棵树的内容就是当前正被显示在屏幕上的内容
这三棵树的逻辑结构都是一样的区别只是有各自的属性
UITableView
UITableView代理需要遵循的两个协议以及必须实现的方法
主要是通过2个协议:UITableViewDataSource和UITableViewDelegate
UITableView需要一个数据源代理dataSource来显示数据UITableView会向数据源查询一共有多少行数据以及每一行显示生命数据。没有设置数据源的UITableView只是一个空壳。凡是遵循UITableViewDataSource协议的OC对象都可以是UITableView的数据源我们也需要为UITableView设置代理对象delegate)以便在UITableView触发某些事件时做出相应的处理。凡是遵循了UITableViewDelegate协议的OC对象都可以是UITableView的代理对象一般会让控制器充当充当UITableView的dataSource和delegate我们可以手动实现协议中的某些方法来完成UITableView的实现。
protocol UITableViewDataSource的方法
//返回组数
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView//返回每组里的行数
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section //cell的实现
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath // section头的title(例如,通讯录不同姓名标识)
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section// section尾段的title
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section// section索引的title集合(例如,通讯录索引,帮助快速找到姓名)
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView{}protocol UITableViewDelegateNSObject, UIScrollViewDelegate的方法
//点击cell事件
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
// 设置cell行高(因为参数是indexPath,所以可以设置不同section的行高,也能设置同一section不容row的行高)
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
// section头部的height
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
// section尾部的height
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
// section头部的view
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
// section尾部的view
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section其中代理对象必须实现的方法是DataSource协议的方法
//每组的cell
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
//实现cell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;几个主要的方法在TableView加载时的执行顺序
numberOfSectionsInTableViewnumberOfRowsInSectioncellForRowAtIndexPathheightForRowAtIndexPath
TbaleView的cell的复用
在滑动tableView时为了避免cell的重复的销毁创建而消耗性能就出现了cell的复用
简单来说当创建了多个cell且屏幕显示不了所有的cell系统会将已经创建不在显示的cell放入复用池中当需要新建cell并且标识符与复用池中的标识符存在相同的情况就会调用复用池中的cell并且使用该cell的所有cell控件
cell的两种复用机制
自定义cell
cell复用会出现的问题
对于不同种类的自定义cell有时复用会出现问题。如果自定义cell上的控件不同会出现复用到控件不同的cell这时就会出现界面的错乱。
解决方法
弃用重用机制 - 从indexpath每次获取新的cell对于不同种类的cell设置不同的标识符通过不同的标识符去复用相应类型的cell在prepareForReuses中重置所有的subView的显示属性为nil。当前已经被分配的cell如果被重用了会调用cell的prepareForReuse通知cell tableViewCell的行高计算
动态计算-缓存高度
动态计算
实际开发中使用最多的应该是动态计算cell高度。例如标题高度不固定标签不固定这样就需要更具model里的内容计算行高 使用的时候在tableView的代理设置
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {WMSearchResultQAModel *model self.dataArray[indexPath.row];return [WMSearchResultQAModel calutWholeCellHeightWithModel:model];
}这样就可以达到每个cell根据内容展示不同高度的要求了。 这种方法很繁琐但是也是最精确的最可控的
缓存行高
当tableView滚动的时会不停的调用heightForAtIndexPath这个代理方法当cell的高度需要自适应时就意味着每次回调这个方法都要计算高度而计算是非常消耗时间的就会产生卡顿。
为了避免重复且无意义的计算cell高度缓存高度就释放重要。
缓存高度机制
缓存高度需要一个容器来保存高度数值可以是model 一个可变数组一个可变字典以达到每当回调 heightForRowAtIndexPath 这个方法时我们先去这个缓存里去取如果有就直接拿出来如果没有就计算高度并且缓存起来。
以model为例
在model里声明个cellheight属性用于保存Model对应的cell高度然后在heightForRowAtIndexPath 方法中如果当前model的cellHeight为0说明这个cell没有缓存过高度则计算Cell的高度并把这个高度记录在model的cellHeight。如果当前model的cellHeight不为0则直接使用。 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {WMSearchResultQAModel *model self.dataArray[indexPath.row];if (model.cellHeight 0) {// 有缓存的高度取出缓存高度return model.cellHeight;}// 没有缓存时计算高度并缓存起来CGFloat cellHeight; [WMSearchResultQAModel calutWholeCellHeightWithModel:model];// 缓存给modelmodel.cellHeight cellHeight;return cellHeight;
}手动计算高度的两个方法
sizeToFitboundingRectWithSize
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];可以实现label的最多展开并可获得相应的高度。
注意事项
调整大小sizeToFit 调整视图的大小以适应内容。不改变位置sizeToFit 不会更改视图的位置。确保内容已设置调用 sizeToFit 之前应设置好视图的内容。
doundingRectWithSize
返回文本会在所占据的矩形空间
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;//用算出来的结果显示labellabelSecond.frame CGRectMake(10,200, 350, titleHeight);//将label作为子视图添加到父视图[self.view addSubview:labelSecond];自适应行高-缓存高度
在 iOS8 之后系统结合autolayout提供了动态结算行高的方法 UITableViewAutomaticDimension做好约束我们都不用去实现 heightForRowAtIndexPath 这个代理方法了。
实现步骤 1tableView设置
// 预设行高
self.tableView.estimatedRowHeight xxx;
// 自动计算行高模式
self.tableView.rowHeight UITableViewAutomaticDimension;2在自定义cellmasonry布局
- (void)layoutSubviews {[super layoutSubviews];[self.headImgView mas_makeConstraints:^(MASConstraintMaker *make) {make.top.left.offset(kSpace15);make.size.mas_equalTo(CGSizeMake(50.f, 50.f));// 在自动计算行高模式下 要加上的 make.bottom.equalTo(self.contentView.mas_bottom).offset(-kSpace15);}];[self.nickNameLabel mas_makeConstraints:^(MASConstraintMaker *make) {make.left.equalTo(self.headImgView.mas_right).offset(12.f);make.top.offset(17.f);}];[self.jobWorkLabel mas_makeConstraints:^(MASConstraintMaker *make) {make.left.equalTo(self.nickNameLabel.mas_right).offset(8.f);make.right.lessThanOrEqualTo(self.contentView.mas_right).offset(-kSpace15);make.top.offset(21.f);}];[self.hospitalLabel mas_makeConstraints:^(MASConstraintMaker *make) {make.left.equalTo(self.headImgView.mas_right).offset(12.f);make.top.equalTo(self.jobWorkLabel.mas_bottom).offset(6.f);}];[self.line mas_makeConstraints:^(MASConstraintMaker *make) {make.left.right.bottom.offset(0);make.height.mas_equalTo(0.5f);}];
}所有子控件都要依赖self.contentView作为约束父控件关键控件要做buttom约束确定好控件的上下边界根据控件的动态内容将cell纵向撑开。
3最关键的一步 [cell layoutIfNeeded]
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {WMDoctorEvaluateDescribeInputCell *cell [tableView dequeueReusableCellWithIdentifier:[WMDoctorEvaluateDescribeInputCell reuseIdentifier] forIndexPath:indexPath];kWeakSelfcell.describeInputBlock ^(NSString * _Nonnull describeText) {weakSelf.inputDescribeText describeText;};//关键的一步,解决不正常显示问题[cell layoutIfNeeded];return cell;
}缓存高度机制
首先获取cell实际显示的高度
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{NSString *key [NSString stringWithFormat:%ld, (long)indexPath.row];[self.heightDict setObject:(cell.height) forKey:key];NSLOG(第%行的计算的最终高度是%f,key,cell.height);
}didEndDisplayingCell当cell已经被正真的显示到屏幕上时会调用这个方法此时的高度是cell的正真高度。根据indexPath.row作为key将高度缓存进字典。
然后在 heightForRowAtIndexPath 方法里判断如果字典里有值则使用缓存高度否则自动计算
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{NSString *key [NSString stringWithFormat:%ld,indexPath.row];if (self.heightDict[key] ! nil) {NSNumber *value _heightDict[key];return value.floatValue;}return UITableViewAutomaticDimension;
}ViewController的生命周期
ViewController相关函数以及执行顺序
当一个视图被创建并且在屏幕上显示的时候
1.alloc 创建对象分配空间 2. init; 初始化对象初始化数据 3. loadview 在UIViewController对象的view被访问且为空的时候调用 4. viewDidLoad 控制器载入完成可以进行自定义数据以及动态创建其他控件 5. viewWillAppear 视图出现在屏幕之前马上这个视图就会被展现在屏幕上了 6. viewWillLayoutSubviews 该方法在通知控制器将要布局 view 的子控件时调用 7. viewDidLayoutSubviews 该方法在通知控制器已经布局 view 的子控件时调用 8. viewDidAppear 视图已在屏幕上渲染完成
当一个视图被移除屏幕并且销毁的时候代码执行的顺序
1viewWillDisappear 视图将被从屏幕上移除之前执行 2viewDidDisappear 视图已经被从屏幕上移除用户看不见这个视图了 3dealloc 视图被销毁释放在init和viewDidLoad中创建的对象
离屏渲染
图片渲染和显示的流程 在Application阶段CPU会创建我们的视图计算视图的一些数据进行编解码绘制纹理等操作然后交给GPUGPU先通过顶点着色器去确定图像在硬件上的上的具体显示位置然后通过片源着色器计算每个像素点的颜色值最后通过光栅化找到像素点的位置并把颜色显示上去。最终转化为一个个屏幕像素。然后把渲染后的数据放到帧缓存区FrameBuffer)然后视图控制器就会读取数据交给显示器显示
掉帧
通过屏幕扫描的方式会通过CRT电子枪从上到下逐行扫描这个扫描的过程就是读取帧缓存区里的数据当它扫描完的时候它就会显示一帧的画面 当显示完一帧画面后CRT电子枪又回到原来的位置继续重新扫描显示下一帧。当一个垂直同步信号过来的时候如果说CPU和GPU还没有完成渲染的结果去做提交也就是没有把数据放到FrameBuffer里面这种情况未过来提交过来这一帧的画面就会被丢弃然后等待下一次垂直同步信号过来再来显示新的画面这个过程被称为掉帧。
离屏渲染
当视图比较复杂的时候GPU无法扫描视图的全部内容并把渲染数据反在FrameBuffer中无法显示画面的全部内容就需要开辟一块二外的内存缓存区把剩下的渲染数据放入其中再合并到FrameBuffer中最后进行视图的显示。
导致离屏渲染的操作
添加光栅化添加遮罩添加阴影抗锯齿设置背景颜色和圆角不透明