10.项目实战 百思不得姐 设置图片,视频,声音图片控件的内容

@(iOS 项目实战)[项目实战]


目录

  • 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

    10.项目实战 百思不得姐 设置图片,视频,声音图片控件的内容_第1张图片
    使用xib布局帖子的cell.png

计算帖子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
          // 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的滚动而不断变化.

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;
    }
}

  • 帖子初步运行效果

    10.项目实战 百思不得姐 设置图片,视频,声音图片控件的内容_第2张图片
    帖子初步运行效果.gif

  • 如果不在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,按比例填充.
    10.项目实战 百思不得姐 设置图片,视频,声音图片控件的内容_第3张图片
    xib布局声音和视频图片控件.png

设置声音图片控件的内容

  • 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
          
      }
    

  • 声音,视频类型的帖子运行效果
    • 红色部分是还未实现的图片控件.
10.项目实战 百思不得姐 设置图片,视频,声音图片控件的内容_第4张图片
声音视频类型帖子运行效果.gif

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;
          }
      }
    

  • 声音,视频,图片帖子运行效果

声音,视频,图片类型帖子运行效果.gif

存在问题

  • 滚动会有卡顿
  • 4s真机测试频繁出现内存警告

补充

xib命名时需注意

  • xib命名需注意,避免控制器加载的时候加载该xib.

    • 使用[[WXPictureViewController alloc] init]方法创建控制器是会自动加载WXPictureViewController.xib,WXPictureView.xib.所以使用xib自定义view时,要注意命名,避免控制器加载的时候加载到该xib的view.
  • 加载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来存储已下载的图片.

你可能感兴趣的:(10.项目实战 百思不得姐 设置图片,视频,声音图片控件的内容)