不等高cell的搭建(一)
一.界面搭建
1.确定开发模式
如果界面是固定的,可以用xib
界面的一些内容不固定,就用纯代码
cell用什么方式去开发(我们采用纯代码和xib结合的方式)
2.划分层次结构
2.1 怎么划分?
按照功能划分
按照隐藏效果:在某些条件下,一些控件要一起隐藏,就可以放在同一模块中(前提是这些控件集中在一起)
2.2 分析界面发现cell的middleView和commentView不确定要不要显示
所以,cell采用纯代码方法搭建
3.自定义cell
3.1在创建cell的时候,就把所有可能显示的模块全部添加到cell上
把所有可能出现的模块(控件)全部添加到cell里面,然后根据需求再决定某些模块(控件)是显示还是隐藏
3.2根据服务器数据,决定某些模块是否隐藏
3.3在哪里创建cell?
采用注册的方法,底层会自动调用initWithStyle方法来创建cell
4.一个模块一个模块去搭建
4.1把cell分解为四个模块,如上图
4.2先把一个模块所有的内容(搭建界面)和业务逻辑处理完,在去处理下一个模块
4.3模块的处理方式:先搭建界面,再处理界面业务逻辑
4.4优点:业务逻辑清晰,不容易混淆
二.topView模块开发
1.xib搭建界面
1.1分析界面有哪些控件来搭建
1.2自定义uiview并创建xib,描述好界面后,把界面添加到cell里面
2.请求数据
2.1 查看接口文档,使用afn发送网络请求
2.2网络请求成功时,把数据转换成模型
2.3在模型中定义属性(先自定义模型)
查看界面需要使用哪些数据,就找到定义数据的属性定义到模型中
2.3自定义视图模型,视图模型包含模型(就是在视图模型中定义一个模型的属性)
2.4遍历模型,全部转换为视图模型
创建视图模型对象
用视图模型的模型属性来接收模型
把视图模型保存到数组中
3.在视图模型中计算cell子控件的frame和高度
3.1什么时候计算的
在模型转视图模型的时候计算的
3.2在哪里计算的?
重写视图模型的模型属性的set方法,在方法里面计算cell的frame和高度,并定义属性保存起来
因为,在模型转视图模型的时候,会把模型传给视图模型的模型属性,底层会触发视图模型的模型属性set方法,这个时候就去计算了
3.3 cell的高度计算
cell的高度就是Y轴方向最后一个控件的最大Y值
4.展示数据
4.1怎么展示?
在自定义cell里面定义视图模型属性
创建cell之后,从数组中取出对应的视图模型,给cell的视图模型属性赋值(触发视图模型的set方法)
4.2模型怎么接收数据的?
在cell视图模型属性的set方法里面,设置cell的frame和cell的高度(直接从视图模型中去)
在把视图模型的数据直接赋值给模型(触发模型的set方法)
重写模型的set方法,然后给界面的UI元素赋值,这样就能在界面上看到数据了
5.注意点
5.1 cell会循环利用,设置某项属性后,一定要记得还原
5.2 运行会报一些莫名其妙的约束冲突
是系统的自动拉伸约束有冲突,取消掉就可以了
三.middleView模块搭建
middleView模块的样式也分很多种
我们可以利用xib搭建不同样式的middleView,然后根据服务器数据决定显示哪种样式的middleView
注意:一定要把其它样式的middleView隐藏,还需要在其它地方还原,防止循环引用
根据需求我们可以把middleView分为四种样式:
1.textView样式
也就是没有middleView,这种样式只要不显示middleView就可以了(把pictureView,videoView,voiceView隐藏)
也不需要计算frame的高度了
2.pictureView样式
2.1 分析界面有哪些控件组成, 界面怎么搭建?
加载图片的时候:有一个gif图标,用ImageView来搭建,进度条可以放在一个view里面
文字部分:如果是图片用ImageView,如果是文字,用label或button都行, 需要与用户交互就用button
显示图片时:图片用ImageView搭建,下面的查看大图用button(有图片和文字,还有背景图片,还要与用户交互,只能用button)
由于界面是固定的,可以采用xib来搭建
2.2 把pictureView添加到cel里面
2.3 请求数据
2.4 在视图模型中计算cell子控件的frame和高度
2.4.1首先判断数据是什么类型的
返回的类型是NSInther,可读性太差,可以定义枚举来替代,可读性就很好
1 typedef enum : NSUInteger { 2 XTThemeItemTypeAll = 1, 3 XTThemeItemTypeVideo = 41, 4 XTThemeItemTypeVoice = 31, 5 XTThemeItemTypePicture = 10, 6 XTThemeItemTypeText = 29 7 } XTThemeItemType;
注意:定义枚举的快捷键是:enum
2.4.2 如果是text类型的就不需要计算了
2.4.3 如果图片实际高度太高,我们就在模型里面定义一个属性来记录是否是大图
2.4.4 给大图设置一个固定高度,不需要把大图全部展示出来了
1 // middleView:一样计算方式 2 // 不是段子的时候,才需要计算中间View的Frame 3 if (item.type != XMGThemeItemTypeText) { 4 CGFloat middleW = textW; 5 CGFloat middleH = middleW / item.width * item.height; 6 if (middleH > XTScreenH) { // 大图 7 middleH = 300; 8 item.is_bigPicture = YES; 9 } 10 CGFloat middleX = margin; 11 CGFloat middleY = _cellH; 12 _middleViewFrame = CGRectMake(middleX, middleY, middleW, middleH); 13 _cellH = CGRectGetMaxY(_middleViewFrame) + margin; 14 }
2.5 运行发现,如果是大图,图片就被压缩了,很难看,怎么解决?
ImageView默认是图片填充的,就是把图片拉伸或压缩成整个ImageView的大小
设置ImageView的内容模式,不让它压缩 UIViewContentModeTop
2.6 设置内容模式,发现虽然图片不被压缩,但是图片不能填充ImageView(两边是空的) 怎么解决?
UIViewContentModeTop默认是图片上面显示到ImageView的顶部,但是不会进行拉伸或压缩
我们需要先对图片进行拉伸或压缩处理,然后再设置 UIViewContentModeTop 就可以了
2.7 怎么对图片进行拉伸或压缩处理?
可以用绘图,生成一张新的拉伸或压缩好的图片
2.8 怎么绘图?
2.8.1 开启图形上下文
2.8.2 绘制图片
2.8.3 获取新的图片
2.8.4 关闭图形上下文
2.9 对图片处理进行性能优化
2.9.1 每次设置图片,都要进行绘图,有可能重复绘图,性能不好,怎么优化?
把处理好的图片缓存到磁盘(沙盒)里面,下次再设置这张图片时,直接从磁盘取
2.9.2 怎么对图片缓存处理?
自己写方法太麻烦, SDImageCache.h内部有方法缓存图片,直接调用它的方法来进行缓存
1 [[SDImageCache sharedImageCache] storeImage:image forKey:item.image0]; 2 注意:要到入头文件: #import <SDImageCache.h>
2.9.3 怎么从磁盘中去图片?
同样也是调用SDImageCache.h方法来取
UIImage *image = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:item.image0];
注意:key值就是图片的url
2.10 刚开始加载图片时,进度条怎么设置?
2.10.1 需要设置一个功能模块的时候,我们要先去找有没有做该功能模块的框架,有就直接拿来用
2.10.2 没有的话,就找一个功能差不多的模块来修改一下
2.10.3 怎么去查找一个框架?
1 - (void)awakeFromNib 2 { 3 _progressView.progressLabel.textColor = [UIColor whiteColor]; 4 _progressView.progressTintColor = [UIColor whiteColor]; 5 _progressView.roundedCorners = 10; 6 7 _progressView.progressLabel.text = [NSString stringWithFormat:@"%.1f%%",0.0 * 100]; 8 _progressView.progress = 0; 9 } 10 11 - (void)setItem:(XMGThemeItem *)item 12 { 13 _item = item; 14 15 // 设置图片 16 UIImage *image = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:item.image0]; 17 if (image) { 如果有图片直接设置,并把进度设为100% 18 _imageView.image = image; 19 _progressView.progressLabel.text = [NSString stringWithFormat:@"%.1f%%",1.0 * 100]; 20 _progressView.progress = 1; 21 } else { 如果没有图片,就先把进度设置为0 ,再去下载图片 22 _progressView.progressLabel.text = [NSString stringWithFormat:@"%.1f%%",0.0 * 100]; 23 _progressView.progress = 0; 24 // 下载网络图片 25 [_imageView sd_setImageWithURL:[NSURL URLWithString:item.image0] placeholderImage:nil options:SDWebImageRetryFailed progress:^(NSInteger receivedSize, NSInteger expectedSize) { 26 进度就为已下载的文件大小 / 总文件大小 27 CGFloat progress = 1.0 * receivedSize / expectedSize; 28 运行发现,进度有时候为-0,打印发现总文件大小该开始为-0 当为-0 的时候就返回 29 注意:该block会频换调用,所以 returen一次没关系 30 if (expectedSize < 0) return ; 31 _progressView.progressLabel.text = [NSString stringWithFormat:@"%.1f%%",progress * 100]; 32 _progressView.progress = progress; 33 34 } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { 35 36 if (!item.is_bigPicture) return ; 37 38 // 生成一个新拉伸好图片 39 CGFloat w = XMGScreenW - 20; 40 CGFloat h = w / item.width * item.height; 41 42 // 开启图形上下文 43 UIGraphicsBeginImageContextWithOptions(CGSizeMake(w, h), NO, 0); 44 // 绘图 45 [image drawInRect:CGRectMake(0, 0, w, h)]; 46 // 获取上下文图片 47 image = UIGraphicsGetImageFromCurrentImageContext(); 48 // 保存图片到沙盒 49 [[SDImageCache sharedImageCache] storeImage:image forKey:item.image0]; 50 // 关闭上下文 51 UIGraphicsEndImageContext(); 52 53 _imageView.image = image; 54 }]; 55 } 56 根据服务器数据,决定gif图片是否显示 57 _gifView.hidden = !item.is_gif; 58 根据自己判断保存的属性,来决定显示大图按钮是否显示 59 _seeBigButton.hidden = !item.is_bigPicture; 60 61 if (item.is_bigPicture) { 62 _imageView.contentMode = UIViewContentModeTop; 63 _imageView.clipsToBounds = YES; 64 } else { 65 _imageView.contentMode = UIViewContentModeScaleToFill; 66 _imageView.clipsToBounds = NO; 67 } 68 }
3.videoView样式 和 voiceView样式
3.1 由于这两个模块基本相同,处理方法也差不多
注意:把搭建好的xib的view拷贝到另一个xib里面的时候,会把对应的连线也拷过去,一定要先删除这些连线,再继续使用
3.2 根据类型,只需要设置中间按钮的图片就可以了
3.3 xib搭建界面,并把界面添加到cell里面
3.4 请求数据
3.5 在视图模型中计算cell子控件的frame和高度
3.6 展示数据
3.6.1 在videoView或voiceView的模型里设置数据的时候,要对数据进行处理
3.6.2 为什么要处理?
服务器返回的时间数据是以秒为单位的,需求以 00:00 来显示
3.6.3 怎么处理?
1 NSInteger minute = item.voicetime / 60; 2 NSInteger second = item.voicetime % 60; 3 self.timeLabel.text = [NSString stringWithFormat:@"%02ld:%02ld",minute,second];
4 在cell模型属性的set方法里面统一设置middleView各个模块的frame的是否隐藏
1 // middleView 2 if (vm.item.type == XMGThemeItemTypePicture) { // 图片 3 _pictureView.hidden = NO; 4 _videoView.hidden = YES; 5 _voiceView.hidden = YES; 6 7 _pictureView.item = vm.item; 8 _pictureView.frame = vm.middleViewFrame; 9 10 } else if (vm.item.type == XMGThemeItemTypeVideo) { // 视频 11 _pictureView.hidden = YES; 12 _videoView.hidden = NO; 13 _voiceView.hidden = YES; 14 15 _videoView.item = vm.item; 16 _videoView.frame = vm.middleViewFrame; 17 } else if (vm.item.type == XMGThemeItemTypeVoice) { // 音频 18 _pictureView.hidden = YES; 19 _videoView.hidden = YES; 20 _voiceView.hidden = NO; 21 22 _voiceView.item = vm.item; 23 _voiceView.frame = vm.middleViewFrame; 24 25 } else { 26 _voiceView.hidden = YES; 27 _pictureView.hidden = YES; 28 _videoView.hidden = YES; 29 }