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 參考資料

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 原文地址: RAYWENDERLICH 說明:英文水平有限,主要是為了鞏固學到的知識,也能幫別人快速上手,節約時間...
    Hi川閱讀 4,254評論 1 6
  • 自適應Table View Cells 注意:這篇教程支持最新的Xcode 7.3,iOS 9和Swift 2.2...
    張嘉夫閱讀 3,054評論 9 50
  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,200評論 4 61
  • 琉璃星影墜, 疑似銀河垂。 愁心與明月, 伴君子夜歸。
    憂傷的臉龐閱讀 127評論 0 0
  • 就看到我 就抱緊我 勇敢地向我走 最后一步 光明敞亮的地方 我卻感覺不到新鮮 曾幾何時 你跟我說 重要的不是風景 ...
    子瑜曰閱讀 152評論 0 0