iOS学习笔记(6)-自适应高度的Table View

这篇笔记主要记录了完成一个自适应高度的TableView的例子。例子来自https://www.raywenderlich.com/87975/dynamic-table-view-cell-height-ios-8-swift,由于原文用的是swift,与最新版的语法有所不同,我把这个例子用Objective-C改写了。demo的代码地址:
https://github.com/shishujuan/ios_study/tree/master/tableview/TableViewDemo.

1 概览

这个例子主要完成了一个表格视图的展示,其中每个单元格内容可能不同,高度也可能不一样,需要动态适应。这个demo做的事情就是从一个RSS地址拉取数据,然后在表格展示。主要知识点有AutoLayoutUITableView以及CocoaPods的使用。AutoLayout前一篇文章已经有分析过,UITableView是本文的重点,后面会介绍。

而CocoaPods新出现的一个东东,它是iOS开发中一个第三方库的依赖管理工具,类似于java中的mvn和nodejs中的npm等。详细信息可以参见唐巧大大的文章介绍[用CocoaPods做iOS程序的依赖管理。我这个demo中用到的Podfile内容如下,可以看到这个demo中需要用到AFNetworkActivityLogger, MediaRSSParserMBProgressHUD这三个库。AFNetworkActivityLogger是AFNetworking2.0的一个扩展,是开发中很常用的第三方库,封装了一些网络请求,可以大幅简化我们的代码。而MediaRSSParser是这个demo才用到,用于解析RSS文件。MBProgressHUD也是很常用的一个库,用于提示进度,类似好比我们自己手写的那些ActivityIndicator,当然比我们手写要方便很多。

我们只需要在工程目录下运行pod install,就会下载好第三方库了。如果运行命令卡住了的话,可以用这个命令pod install --verbose --no-repo-update.

platform :ios, '8.0'

pod 'AFNetworkActivityLogger', '~> 2.0'
pod 'MediaRSSParser', '~> 1.0'
pod 'MBProgressHUD', '~> 0.9'

2 创建Scene

本demo创建了3个Scene(除去导航栏),Storyboard界面如下:

图1 初始Storyboard

其中Deviant Art为列表页面,Deviant Article为不带图片的详情页,而Deviant Media则为带图片的详情页。其中:

  • Devian Art顶部有一个嵌入的Navigation Bar(导航栏,边上有一个刷新按钮用于重新加载数据),下面有一个Text Field控件的搜索框,搜索框下面是一个Table View,用于展示作品列表。注意,我们这里还没有添加Table View Cell,稍后我们会看到怎么自定义这个Cell。
  • Devian Article为不含图片的作品详情页。这个场景只有两个Label,分别是标题和副标题。
  • Devian Media为带图片的作品详情页。这个场景除了标题和副标题两个Label外,还有一个Image View。

3 解析RSS

解析RSS借助了第三方库RSSParser来实现。代码如下:

(void)parseForQuery:(NSString *)query {
    [self showProgressHUD];
    RSSParser *parser = [[RSSParser alloc] init];
    [parser parseRSSFeed:deviantArtBaseStringUrlString parameters:[self parametersForQuery:query] success:^(RSSChannel *channel) {
        [self convertItemPropertiesToPlainText:channel.items];
        self.items = channel.items;
        [self hideProgressHUD];
        [self reloadTableViewContent];
    } failure:^(NSError *error) {
        [self hideProgressHUD];
        NSLog(@"Error:%@", error);
    }];
}
- (void)showProgressHUD {
    MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:true];
    hud.labelText = @"Loading";
}
- (void)hideProgressHUD {
    [MBProgressHUD hideHUDForView:self.view animated:true];
}

其中showProgressHUD和hideProgressHUD函数用到了第三方库MBProgressHUD来实现,省去了我们自己去添加Activity Indicator。解析RSS获取到了作品列表,我们需要展示在Table View中,因此接下来我们就要添加自定义的Table View Cell。

4 创建Basic Cell

添加一个Table View Cell到Table View中,并在属性标签中设置Class为BasicCell,设置初始的rowHeight为83。其中内容为两个Label(Title Label字体大小为17,Subtitle Label为15,包含图片的Cell我们在后面再添加),如下所示:

图2 Basic Cell

约束关系如下所示:

图3 Title Label约束
图4 Subtitle Label约束

然后设置两个Label的抗压缩和抗拉伸优先级。这里设置Title Label的抗压缩和抗拉伸优先级为751,高于Subtitle Label的750,也就是说要优先满足Title Label的约束:

图5 Title Label的抗压缩抗拉伸优先级设置
图6 Subtitle Label的抗压缩抗拉伸优先级设置

5 配置Table View

为了动态设置Cell的高度,先要配置下Table View的几个属性。如下所示,设置了估算高度以及rowHeight属性,此外,设置Table View的delegate和dataSource为View Controller自身。注意,需要设置View Controller的automaticallyAdjustsScrollViewInsets为NO,否则会看到Table View与Text Field之间有一段空白。

- (void)configureTableView {
    //设置rowHeight为AutomaticDimension是为了让Table View通过Auto Layout的约束去定义每个Cell的高度。
    self.feedTableView.rowHeight = UITableViewAutomaticDimension;     
    //设置estimatedRowHeight为了提高Table View的渲染效率,这是一个估算高度。
    self.feedTableView.estimatedRowHeight = 160.0;
    self.feedTableView.delegate = self; 
    self.feedTableView.dataSource = self;
    self.searchTextField.delegate = self;

    //注意,不设置这个会导致Table View与searchTextField有一段空白。
    self.automaticallyAdjustsScrollViewInsets = NO; 
}

接下来实现dataSource和deletegate的方法。

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [self.items count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    return [self basicCellAtIndexPath:indexPath];
}

- (BasicCell *)basicCellAtIndexPath:(NSIndexPath *)indexPath {
    BasicCell *cell = [self.feedTableView dequeueReusableCellWithIdentifier:basicCellIdentifier];
    [self setTitleForCell:cell indexPath:indexPath];
    [self setSubtitleForCell:cell indexPath:indexPath];
    return cell;
}

- (void)setTitleForCell:(BasicCell *)cell indexPath:(NSIndexPath *)indexPath {
    RSSItem *item = self.items[indexPath.row];
    cell.titleLabel.text = item.title ? item.title : @"[No Title]";
}

- (void)setSubtitleForCell:(BasicCell *)cell indexPath:(NSIndexPath *)indexPath {
    RSSItem *item = self.items[indexPath.row];
    NSString *subTitleText = item.mediaText ? item.mediaText : item.mediaDescription;
    if (subTitleText) {
        subTitleText = subTitleText.length > 200 ? [subTitleText substringToIndex:200] : subTitleText;
    } else {
        subTitleText = @"";
    }
    cell.subtitleLabel.text = subTitleText;
}

6 添加Image Cell

为了显示图片,需要新增加一个Table View Cell,这里设置标识为Image Cell。与Basic Cell不同的是,需要增加一个Image View用来显示图片。添加的约束如下所示,具体参见例子代码:

图7 Image Cell约束

这里有几个地方要注意一下:

  • Image View的宽度和高度都设置的100pt,注意,这两个约束的优先级这里设置为999。此外,Image View与Cell底部的距离>=20这个约束的优先级也设置为999。
  • Subtitle Label与Cell底部的距离 >= 20 这个约束的优先级为1000。也就是说要优先满足Subtitle Label的约束。

接下来需要设置Image Cell,相关代码如下:

//修改cellForRowAtIndexPath方法,区分有图片还是没图片分开处理。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    if ([self hasImageAtIndexPath:indexPath]) {
        return [self imageCellAtIndexPath:indexPath];
    } else {
        return [self basicCellAtIndexPath:indexPath];
    }
}

- (BOOL)hasImageAtIndexPath:(NSIndexPath *)indexPath {
    RSSItem *item = self.items[indexPath.row];
    NSArray<RSSMediaThumbnail *> *mediaThumbnailArray = [item mediaThumbnails];
    
    for (RSSMediaThumbnail *mediaThumbnail in mediaThumbnailArray) {
        if (mediaThumbnail.url != nil) {
            return YES;
        }
    }
    return NO;
}

- (ImageCell *)imageCellAtIndexPath:(NSIndexPath *)indexPath {
    //在Storyboard中已经设置了ImageCell标识,
    //所以这里是肯定可以取得cell的,可以省去是否为nil的判断。
    ImageCell *cell = [self.feedTableView dequeueReusableCellWithIdentifier:imageCellIdentifier];
    [self setImageForCell:cell indexPath:indexPath];
    [self setTitleForCell:cell indexPath:indexPath];
    [self setSubtitleForCell:cell indexPath:indexPath];
    return cell;
}

- (void)setImageForCell:(ImageCell *)cell indexPath:(NSIndexPath *)indexPath {
    RSSItem *item = self.items[indexPath.row];
    RSSMediaThumbnail *mediaThumbnail;
    if (item.mediaThumbnails.count >= 2) {
        mediaThumbnail = item.mediaThumbnails[1];
    } else {
        mediaThumbnail = item.mediaThumbnails[0];
    }
    
    //预设图片为nil,防止之前的图片重用导致看起来图片错乱
    cell.customImageView.image = nil;
    
    if (mediaThumbnail.url != nil) {
        [cell.customImageView setImageWithURL:mediaThumbnail.url];
    }
}

7 其他

另外需要设置Basic Cell和Image Cell到对应作品详情视图的segue,这个具体参见代码。另外,对Table View的Cell高度的估算也在最终代码有优化。最终运行效果如下图所示:

图8 运行效果

8 参考资料

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,186评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,858评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,620评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,888评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,009评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,149评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,204评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,956评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,385评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,698评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,863评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,544评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,185评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,899评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,141评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,684评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,750评论 2 351

推荐阅读更多精彩内容

  • 原文地址: RAYWENDERLICH 说明:英文水平有限,主要是为了巩固学到的知识,也能帮别人快速上手,节约时间...
    Hi川阅读 4,194评论 1 6
  • 自适应Table View Cells 注意:这篇教程支持最新的Xcode 7.3,iOS 9和Swift 2.2...
    张嘉夫阅读 3,016评论 9 50
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,067评论 4 62
  • 琉璃星影坠, 疑似银河垂。 愁心与明月, 伴君子夜归。
    忧伤的脸庞阅读 118评论 0 0
  • 就看到我 就抱紧我 勇敢地向我走 最后一步 光明敞亮的地方 我却感觉不到新鲜 曾几何时 你跟我说 重要的不是风景 ...
    子瑜曰阅读 137评论 0 0