@(iOS 项目实战)[项目实战]
- 作者: Liwx
- 邮箱: 1032282633@qq.com
目录
- 10.项目实战 百思不得姐 设置图片,视频,声音图片控件的内容
- 1.计算帖子cell的高度(暂不考虑中间图片控件)
- 计算帖子cell高度的方式一(使用字典缓存方式)
- 计算帖子cell高度的方式一(通过模型数据计算cell高度)
- tableView估算高度的简单使用
- 2.计算中间图片控件的frame
- 计算中间图片控件的frame
- 3.使用xib自定义中间图片控件(图片,声音,视频类型的图片控件)
- 使用xib自定义三种图片控件
- tableView补充
- 4.设置声音,视频图片控件的内容
- 设置声音图片控件的内容
- 封装分类UIImageView+Image,实现通过判断网络状态下载对应的图片
- 5.设置图片控件的内容(长图,动态图,普通图)
- 通过url判断是静态图还是动态图
- 设置图片控件内容
- 存在问题
- 补充
- xib命名时需注意
- SDWebImage补充
1.计算帖子cell的高度(暂不考虑中间图片控件)
因为中间图片控件显示的图片的尺寸(宽高)是由服务器返回的数据提供,所以此次不能使用自动计算高度的方式,需手动计算cell高度.
-
使用xib布局帖子的cell
计算帖子cell高度的方式一(使用字典缓存方式)
-
方式一: 使用
字典缓存
所有模型对应的cell的高度(不推荐)- 使用模型的内存地址作为字典的key.
// 将内存地址转换成字符串,作为字典的key NSString *key = [NSString stringWithFormat:@"%p", topicItem];
- 也可以使用模型的description方法来获取类名+内存地址,作为字典的key.
// description方法来获取类名+内存地址,作为字典的key NSString *key = topicItem.description;
- 缓存cell的高度,无需每次都计算cell高度
- 如果
cell高度不为0
,表示已经计算过cell高度,无需再重复计算.
- 如果
计算帖子cell高度的方式一(通过模型数据计算cell高度)
使用模型数据计算cell高度,为模型额外添加cellHeight属性,用户存放每个模型对应cell的高度,更利于程序封装性.
- 方式二: 通过模型数据计算cell高度
- 1.为WXTopicItem模型
添加cellHeight属性
,用于存放通过模型数据计算的cell高度. - 2.重写cellHeight的get方法,在方法中实现通过模型数据计算cell高度.使用cellHeight累计cell高度.
- 1.顶部view高度55
- 2.文字高度
- 计算文字高度方式一: (过期)使用
sizeWithFont:constrainedToSize:
方法,参数Size表示限制文字显示的范围
. - 计算文字高度方式二: 使用
boundingRectWithSize:options:attributes:attributes context:
方法计算文字高度,options
参数需设置为NSStringDrawingUsesLineFragmentOrigin
.
- 计算文字高度方式一: (过期)使用
- 3.中间显示的图片控件高度(此处暂不考虑)
- 4.最热评论高度 = 最热评论标题高度(20) + 最热评论内容高度
- 5.底部工具条高度
- 底部工具条的高度包括下面灰色分割线高度10
实现分割线方法: 设置tableView的背景色为灰色,重写cell的setFrame:方法,设置每个cell之间的间隔为10
- 底部工具条的高度包括下面灰色分割线高度10
- 1.为WXTopicItem模型
// WXAllViewController.m控制器的viewDidLoad方法中设置tableView背景色,并取消原来的分割线.
self.tableView.backgroundColor = WXColor(206, 206, 206);
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
// WXTopicItem.m文件中重写,设置每个cell之间的间隔为10
- (void)setFrame:(CGRect)frame
{
frame.size.height -= WXMargin;
[super setFrame:frame];
}
-
设置cell的背景图片,需在Assets.xcassets中设置mainCellBackground背景图片为可拉伸图片,或者可以使用代码方式拉伸背景图片最中间的点.
-
注意
: 每个模块间都有10的间距. -
性能优化
: 每次新的cell重新进入视野时,系统会重新调用heightRowAtIndexPath方法
,获取新的cell的高度. - 此处cell的高度是通过模型数据计算的,无需每次都重新计算cell高度.如果cell高度已经计算过,则直接返回cell高度.
- 使用模型数据计算cell高度核心参考代码
// ---------------------------------------------------------------------------- // WXTopicItem.m中重写cellHeight方法,计算cell高度 - (CGFloat)cellHeight { // 如果已经计算过cell的高度,无需再计算 if (_cellHeight) return _cellHeight; // ------------------------------------------------------------------------ // 1.顶部view高度55 _cellHeight += 55; // ------------------------------------------------------------------------ // 2.文字高度 // 2.1 文字最大宽度 = 屏幕宽度 - 2 * 间距 CGFloat textMaxW = screenW - 2 * WXMargin; _cellHeight += [self.text boundingRectWithSize:CGSizeMake(textMaxW, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:15]} context:nil].size.height + WXMargin; // ------------------------------------------------------------------------ // 3.最热评论高度 = 最热评论标题高度(20) + 最热评论内容高度 // 3.1 取出最热评论 NSDictionary *cmt = self.top_cmt.firstObject; if (cmt) { // 3.2 最热评论标题高度20 _cellHeight += 20; // 3.3 最热评论内容高度 NSString *username = cmt[@"user"][@"username"]; NSString *content = cmt[@"content"]; if (content.length == 0) { content = @"[语音评论]"; } NSString *cmtText = [NSString stringWithFormat:@"%@ : %@", username, content]; _cellHeight += [cmtText boundingRectWithSize:CGSizeMake(textMaxW, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:14]} context:nil].size.height + WXMargin; } // ------------------------------------------------------------------------ // 4.底部工具条高度 _cellHeight += 35 + WXMargin; return _cellHeight; }
-
tableView估算高度的简单使用
- 设置tableView中的cell估算高度的两种方式
- 方式一: 统一设置tableView所有cell估算高度
self.tableView.estimatedRowHeight = 100;
- 方式二: 使用代理方法分别设置tableView中每个cell的估算高度
tableView会先多次调用estimatedHeightForRowAtIndexPath:
方法,再调用heightRowAtIndexPath
方法.
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath;
如果没有使用估算高度,tableView显示前会先多次调用heightRowAtIndexPath方法,之后每次cell显示时又会调用heightRowAtIndexPath方法. - 会出现滚动tableView时,
滚动条的长度
会随tableView的滚动而不断变化.
- 方式一: 统一设置tableView所有cell估算高度
2.计算中间图片控件的frame
计算中间图片控件的frame
- 1.为WXTopicItem模型添加图片
高度和宽度属性
,为模型额外添加中间图片控件的frame属性centerFrame
. - 2.在cellHeight的get方法中添加计算中间图片控件高度和frame的处理,计算中间图片控件高度和centerFrame的核心参考代码如下
- 如果是非文字段子,累计中间图片控件高度.
- 计算中间图片控件等比例显示图片
- 计算中间图片控件的centerFrame属性
- _cellHeight属性累计图片控件高度.
// ------------------------------------------------------------------------
// 3.中间图片控件高度,等比例填充方式显示图片
// 3.1 判断如果不是文字段子,累加图片控件高度
if (self.type != WXTopicTypeWord) {
// 3.2 计算图片控件实际的frame
// centerW / centerH = self.width / self.height;
CGFloat centerW = textMaxW;
CGFloat centerH = centerW * self.height / self.width;
CGFloat centerX = WXMargin;
CGFloat centerY = _cellHeight;
self.centerFrame = CGRectMake(centerX, centerY, centerW, centerH);
_cellHeight += centerH + WXMargin;
}
3.使用xib自定义中间图片控件(图片,声音,视频类型的图片控件)
据分析,三种类型的图片控件都是用xib方式创建,无需多处都用[[NSBundle mainBundle] loadNibNamed:方法从xib创建图片控件,可以考虑封装UIView+Init的分类.
UIView+Init分类中封装加载同名xib的类方法
.这样就无需在每个图片控件类中实现快速从xib创建控件的类方法.参考代码如下.
// ----------------------------------------------------------------------------
// 从xib创建view
+ (instancetype)wx_viewFromXib
{
return [[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self) owner:nil options:nil].lastObject;
}
使用xib自定义三种图片控件
- 1.使用xib方式创建图片,声音,视频类型的图片控件类.
- 2.为区分三种不同控件,粗略用不同背景色来区分三种图片控件.在xib中分别设置图片,声音,视频的图片控件背景色为红色,绿色,蓝色.
- 3.
避免重复添加图片控件
,使用懒加载
的方式将三种类型的图片控件添加到cell的contentView
中. - 4.
避免重复利用时,重复添加不同类型的图片控件,出现图片控件既有声音图片控件,又有视频图片控件
.解决方案:在cell的模型set方法setTopicItem:中控制不同类型的帖子的图片控件是否要隐藏/显示
.
// WXTopicCell.m中的setTopicItem:方法中添加控制中间图片控件的隐藏/显示
// ------------------------------------------------------------------------
// 设置中间图片控件的隐藏/显示
if (self.topicItem.type == WXTopicTypePicture) { // 图片
self.pictureView.hidden = NO;
self.voiceView.hidden = YES;
self.videoView.hidden = YES;
} else if (self.topicItem.type == WXTopicTypeVoice) { // 声音
self.pictureView.hidden = YES;
self.voiceView.hidden = NO;
self.videoView.hidden = YES;
} else if (self.topicItem.type == WXTopicTypeVideo) { // 视频
self.pictureView.hidden = YES;
self.voiceView.hidden = YES;
self.videoView.hidden = NO;
} else if (self.topicItem.type == WXTopicTypeWord) { // 文字
self.pictureView.hidden = YES;
self.voiceView.hidden = YES;
self.videoView.hidden = YES;
}
- 5.在
layoutSubviews
方法中重新布局不同类型的帖子要显示的图片控件- 只有cell显示时才会调用cell的layoutSubviews方法进行布局.最好在layoutSubviews方法中设置子控件的frame.
// ----------------------------------------------------------------------------
// 布局子控件
- (void)layoutSubviews
{
[super layoutSubviews];
// ------------------------------------------------------------------------
// 布局中间图片控件
if (self.topicItem.type == WXTopicTypePicture) { // 图片
self.pictureView.frame = self.topicItem.centerFrame;
} else if (self.topicItem.type == WXTopicTypeVoice) { // 声音
self.voiceView.frame = self.topicItem.centerFrame;
} else if (self.topicItem.type == WXTopicTypeVideo) { // 视频
self.videoView.frame = self.topicItem.centerFrame;
}
}
-
帖子初步运行效果
- 如果不在layoutSubviews中设置子控件的frame,可能会出现布局异常.
- 如果
不在layoutSubviews里面设置子控件的frame
,有可能会受到autoresizingMask的影响
,导致子控件的尺寸发生变化
. - 如果要
消除autoresizingMask的影响
,可以设置view.autoresizingMask = UIViewAutoresizingNone;
. -
注意
: UIView创建时,autoresizingMask默认是拉伸宽高. - 以后如果遇到布局不符合预计的效果(
不受控制
)时,应该想到很有可能是autoresizing
问题. -
建议
: 最好在layoutSubviews方法中设置子控件的frame.
- 如果
tableView补充
-
tableView的代理方法调用顺序
- 1.numberOfRowInSection
- 2.heightForRowAtIndexPath
- 3.cellForRowAtIndexPath
-
cell的layoutSubviews方法只有在cell即将显示的时候才会调用,
- 1.numberOfRowInSection
- 2.heightForRowAtIndexPath
- 3.cellForRowAtIndexPath
- 4.最好调用cell的layoutSubviews方法.
4.设置声音,视频图片控件的内容
设置声音和视频图片控件的功能实现方式基本一致.所以只对声音图片控件的实现进行描述,不再对视频图片控件的实现进行累述.
-
使用xib布局声音和视频图片控件
- 在xib中设置占位图片(显示百思不得姐图片)
- 设置占位图片的
imageView的contentMode为Aspect Fit
,按比例填充.
- 设置占位图片的
- 在xib中设置占位图片(显示百思不得姐图片)
设置声音图片控件的内容
1.给WXTopicItem模型添加播放数量,声音文件的播放时长,视频文件的播放时长,小图,大图,中图等属性.
2.声音图片控件WXTopicVoiceView对外提供模型数据
3.在WXTopicCell.m文件的模型set方法setTopicItem:中,
控制voiceView的隐藏/显示处
,为voiceView提供模型数据self.voiceView.topicItem = topicItem;
.-
4.重写模型数据的set方法,设置声音view的数据
- 在模型的set方法设置图片,因该功能多处都会用到,故考虑封装分类.
- 封装通过网络状态判断要下载大图或小图,显示的占位图片的
分类UIImageView+Image
. - 处理并设置播放数量,播放时长.
- 模型set方法实现代码
// ---------------------------------------------------------------------------- // 重写模型数据的set方法,设置声音view的数据 - (void)setTopicItem:(WXTopicItem *)topicItem { _topicItem = topicItem; // ------------------------------------------------------------------------ // 1.设置图片 [self.imageView wx_setLargetImage:topicItem.image1 smallImage:topicItem.image0 placeholder:nil]; // ------------------------------------------------------------------------ // 2.设置播放数量 if (topicItem.playcount >= 10000) { self.playCountLabel.text = [NSString stringWithFormat:@"%.1f万播放",topicItem.playcount / 10000.0]; } else { self.playCountLabel.text = [NSString stringWithFormat:@"%zd播放", topicItem.playcount]; } // ------------------------------------------------------------------------ // 3.设置播放时长 NSInteger minutes = topicItem.voicetime / 60; NSInteger seconds = topicItem.videotime % 60; self.voiceTimeLabel.text = [NSString stringWithFormat:@"%02zd:%02zd", minutes, seconds]; }
封装分类UIImageView+Image,实现通过判断网络状态下载对应的图片
-
封装通过网络状态判断要下载大图或小图,显示的占位图片的
分类UIImageView+Image
.-
注意
: 判断网络状态仅在真机才有效,模拟器不能用.使用预编译宏TARGET_IPHONE_SIMULATOR
,TARGET_OS_IPHONE
来判断当前要运行环境是模拟器
还是真机
.
#if TARGET_IPHONE_SIMULATOR //模拟器 // 模拟器环境实现代码 #elif TARGET_OS_IPHONE //真机 // 真机环境实现代码 #endif
如果是模拟器环境,直接用大图显示
-
如果是真机环境下通过
判断网络状态
来确定要下载大图或小图.- 1.优先判断是否已经下载过大图 ,如果已下载过大图,直接显示大图
- 2.如果为下载过大图,使用AFNetworking的
AFNetworkReachabilityManager
判断网络状态,用于确定要下载大图/小图.
// AFNetworkReachabilityManager判断当前网络状态,如果网络状态变化,会执行block的任务 [[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) { NSLog(@"Reachability: %@", AFStringFromNetworkReachabilityStatus(status)); }]; // 必须启动监听 [[AFNetworkReachabilityManager sharedManager] startMonitoring];
- 3.如果AFNetworkReachabilityManager监听到当前网络状态变化,则根据不同网络状态下载不同图片.
- 1.如果是wifi,则下载大图;
- 2.如果是手机自带3G/4G网络,则下载小图;
- 3.如果没有网络,先判断是否已下载过小图,如果已下载过小图,则显示小图.
- 4.若没有下载过小图,则根据需求如果有占位图片显示占位图片,如果没有占位图片则清空图片.
- 通过
AFNetworkReachabilityManager
判断网络状态下载对应的图片实现参考代码
// ---------------------------------------------------------------------------- // 通过网络状态判断要下载大图或小图,显示的占位图片 - (void)wx_setLargetImage:(NSString *)largetImageUrl smallImage:(NSString *)smallImageUrl placeholder:(UIImage *)placeholder { #if TARGET_IPHONE_SIMULATOR //模拟器 // ------------------------------------------------------------------------ // 1.1 模拟器不同判断网络状态,直接用大图显示 [self sd_setImageWithURL:[NSURL URLWithString:largetImageUrl] placeholderImage:placeholder]; #elif TARGET_OS_IPHONE //真机 // ------------------------------------------------------------------------ // 1.1 在真机环境下通过判断网络状态来确定要下载大图或中图或小图 // 如果已经下载过大图,优先显示大图 UIImage *largeImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:largetImageUrl]; if (largeImage) { // -------------------------------------------------------------------- // 已下载过大图,直接显示大图 self.image = largeImage; } else { // -------------------------------------------------------------------- // 未下载过大图,使用AFNetworking判断网络状态,用于确定要下载大图/小图 __weak AFNetworkReachabilityManager *mgr = [AFNetworkReachabilityManager sharedManager]; __weak typeof(self) weakSelf = self; [mgr setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) { // NSLog(@"%zd", mgr.networkReachabilityStatus); if (mgr.isReachableViaWiFi) { // 如果是wifi,下载大图 [weakSelf sd_setImageWithURL:[NSURL URLWithString:largetImageUrl]]; } else if (mgr.isReachableViaWWAN) { // 如果是手机自带3G/4G网络,下载小图 [weakSelf sd_setImageWithURL:[NSURL URLWithString:smallImageUrl]]; } else { // 没有网络,如果有占位图片显示占位图片,如果没有占位图片则清空图片 // 判断是否已下载过小图 UIImage *smallImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:smallImageUrl]; if (smallImage) { weakSelf.image = smallImage; } else { weakSelf.image = nil; // 根据需求显示占位图片或清空图片 } } }]; [mgr startMonitoring]; } #endif }
-
- 声音,视频类型的帖子运行效果
- 红色部分是还未实现的图片控件.
5.设置图片控件的内容(长图,动态图,普通图)
通过url判断是静态图还是动态图
// gif的url,注意大小写.
http://ww4.sinaimg.cn/large/61129224jw1f139h1eop6g207804ax6q.GIF
http://ww4.sinaimg.cn/large/61129224jw1f139h1eop6g207804ax6q.jpg
-
判断图片是静态图还是动态图
- 方法一: 先将url转成小写, 使用lowercaseString
- 通过后缀名判断, 使用hasSuffix方法
if ([topic.image1.lowercaseString hasSuffix:@"gif"]) ;
- 方法二; 还可以使用pathExtension方法
if ([topic.image1.pathExtension.lowercaseString isEqualToString:@"gif"]) ;
- 方法三: 看下服务器是否有返回图片是否为动态图.服务器有返回参数
is_gif
,该参数是用来判断是静态或动态图. - 方法四: 使用SDWebImage中
NSData的分类方法sd_contentTypeForImageData:方法
判断图片是静态图还是动态图.(图片数据的第一个字节是图片的类型
),该方法必须要下载完图片才能确定.不推荐使用,效率低.
设置图片控件内容
- 1.给WXTopicItem模型添加是否为动态图
is_gif
属性,额外添加是否为长图bigPicture
属性. - 2.在模型cellHeight属性的set方法中,计算中间图片控件高度的地方判断图片是否是长图.
// 超过屏幕高度的图片为长图
if (centerH >= XMGScreenH) {
centerH = 200;
self.bigPicture = YES;
}
3.图片控件WXTopicPictureView对外提供模型数据
4.在WXTopicCell.m文件的模型set方法setTopicItem:中,
控制pictureView的隐藏/显示处
,为pictureView提供模型数据self.pictureView.topicItem = topicItem;
.-
5.重写模型数据的set方法,设置pictureView的数据
- 设置图片控件显示的图片
- 控制左上角gif图片隐藏/显示.
- 控制点击显示大图按钮的隐藏/显示,并控制长图与普通图的imageView的内容模式contentMode和clipsToBounds.
- 图片空间高度变小,内容模式改变为Top模式,超出部分裁减.
- 点击显示大图处理
- 图片控件模型数据的set方法实现参考代码
- (void)setTopicItem:(WXTopicItem *)topicItem { _topicItem = topicItem; // 设置图片 [self.imageView wx_setLargetImage:topicItem.image1 smallImage:topicItem.image0 placeholder:nil]; // 控制gif图片隐藏/显示 self.gifView.hidden = !topicItem.is_gif; // 显示长图按钮 if (topicItem.isBigPicture) { self.seeBigPictureButton.hidden = NO; self.imageView.contentMode = UIViewContentModeTop; self.imageView.clipsToBounds = YES; } else { self.seeBigPictureButton.hidden = YES; self.imageView.contentMode = UIViewContentModeScaleToFill; self.imageView.clipsToBounds = NO; } }
- 声音,视频,图片帖子运行效果
存在问题
- 滚动会有卡顿
- 4s真机测试频繁出现内存警告
补充
xib命名时需注意
-
xib命名需注意,避免控制器加载的时候加载该xib.
- 使用[[WXPictureViewController alloc] init]方法创建控制器是会自动加载
WXPictureViewController.xib
,WXPictureView.xib
.所以使用xib自定义view时,要注意命名,避免控制器加载的时候加载到该xib的view.
- 使用[[WXPictureViewController alloc] init]方法创建控制器是会自动加载
-
加载xib错误信息
- 1.错误信息
-[UIViewController _loadViewFromNibNamed:bundle:] loaded the "WXPictureView" nib but the view outlet was not set. 错误原因:通过加载WXPictureView.xib来创建控制器的view时,并没有在WXPictureView.xib中明确指出控制器的view是谁. // 1> 没有设置File's Owner为控制器 // 2> 没有设置控制器的view属性
- 2.错误信息
-[UITableViewController loadView] loaded the "WXPictureView" nib but didn't get a UITableView. 错误原因:通过加载WXPictureView.xib来创建控制器的view时,没有在WXPictureView.xib中设置控制器的view是tableView(因为当前控制器是一个UITableViewController)
SDWebImage补充
- SDWebImage是
使用url作为key
来存储已下载的图片.