欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

iOS App開發(fā)中使cell高度自適應(yīng)的黑魔法詳解

 更新時(shí)間:2016年03月20日 21:13:22   作者:Vito Zhang  
這篇文章主要介紹了iOS App開發(fā)中使cell高度自適應(yīng)的黑魔法詳解,作者利用iOS8以后的新特性講解了TableView、CollectionView中的cell高度自適應(yīng)以及UITextView輸入內(nèi)容實(shí)時(shí)更新cell高度的方法,需要的朋友可以參考下

在使用 table view 的時(shí)侯經(jīng)常會(huì)遇到這樣的需求:table view 的 cell 中的內(nèi)容是動(dòng)態(tài)的,導(dǎo)致在開發(fā)的時(shí)候不知道一個(gè) cell 的高度具體是多少,所以需要提供一個(gè)計(jì)算 cell 高度的算法,在每次加載到這個(gè) cell 的時(shí)候計(jì)算出 cell 真正的高度。

在 iOS 8 之前

沒有使用 Autolayout 的情況下,需要實(shí)現(xiàn) table view delegate 的 tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat 方法,在這個(gè)方法中計(jì)算并返回 cell 的高度。比如,我有一個(gè)可以顯示任意行數(shù)的純文本 cell,計(jì)算 cell 的代碼可以是這樣:

復(fù)制代碼 代碼如下:

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    let content = self.datas[indexPath.row] as String

    let padding: CGFloat = 20
    let width = tableView.frame.size.width - padding * 2;

    let size = CGSizeMake(width, CGFloat.max)
    let attributes = [NSFontAttributeName: UIFont(name: "Helvetica", size: 14)!]
    let frame = content.boundingRectWithSize(size,
        options: NSStringDrawingOptions.UsesLineFragmentOrigin,
        attributes: attributes,
        context: nil)
    return frame.size.height+1;
}


上面的代碼是一個(gè)最簡(jiǎn)單的例子,這個(gè)例子看起來好像沒有什么問題。但是通過查看這個(gè) delegate 方法的文檔后,可以知道,在每次 reload tableview 的時(shí)候,程序會(huì)先計(jì)算出每一個(gè) cell 的高度,等所有高度計(jì)算完畢,確定了 tableview 的總的高度后,才開始渲染視圖并顯示在屏幕上。這意味著在顯示 table view 之前需要執(zhí)行一堆的計(jì)算,并且這是在主線程中進(jìn)行的,如果計(jì)算量太大程序就很有可能出現(xiàn)卡頓感。比如: table view 的數(shù)據(jù)有上千條,或者計(jì)算高度的代碼中還要先獲取圖片再根據(jù)圖片計(jì)算高度,這些操作都是非常慢的。

如果在 cell 中使用了 autolayout,在計(jì)算 cell 高度時(shí)會(huì)更麻煩。有興趣的可以看這里有篇關(guān)于如何在 autolayout 下動(dòng)態(tài)計(jì)算高度 的文章。

為什么不能等滾動(dòng)到某個(gè) cell 的時(shí)候,再調(diào)用計(jì)算這個(gè) cell 高度的 delegate 呢?原因是 tableview 需要獲得它的內(nèi)容的總高度,用這個(gè)高度去確定滾動(dòng)條的大小等。直到 iOS 7 UITableViewDelegate中添加了新的 API

tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
這個(gè)方法用于返回一個(gè) cell 的預(yù)估高度,如果在程序中實(shí)現(xiàn)了這個(gè)方法,tableview 首次加載的時(shí)候就不會(huì)調(diào)用heightForRowAtIndexPath 方法,而是用 estimatedHeightForRowAtIndexPath 返回的預(yù)估高度計(jì)算 tableview 的總高度,然后 tableview 就可以顯示出來了,等到 cell 可見的時(shí)候,再去調(diào)用heightForRowAtIndexPath 獲取 cell 的正確高度。

通過使用estimatedHeightForRowAtIndexPath 這個(gè) Delegate 方法,解決了首次加載 table view 出現(xiàn)的性能問題。但還有一個(gè)麻煩的問題,就是在 cell 沒有被加載的時(shí)候計(jì)算 cell 的高度,上面給出的代碼中,僅僅是計(jì)算一個(gè) NSString 的高度,就需要不少代碼了。這種計(jì)算實(shí)際上是必須的,然而在 iOS 8 開始,你可能可以不用再寫這些煩人的計(jì)算代碼了!

iOS 8 的魔法

在 iOS 8 中,self size cell 提供了這樣一種機(jī)制:cell 如果有一個(gè)確定的寬度/高度,autolayout 會(huì)自動(dòng)根據(jù) cell 中的內(nèi)容計(jì)算出對(duì)應(yīng)的高度/寬度。

TableView 中的 cell 自適應(yīng)

要讓 table view 的 cell 自適應(yīng)內(nèi)容,有幾個(gè)要點(diǎn):

設(shè)置的 AutoLayout 約束必須讓 cell 的 contentView 知道如何自動(dòng)延展。關(guān)鍵點(diǎn)是 contentView 的 4 個(gè)邊都要設(shè)置連接到內(nèi)容的約束,并且內(nèi)容是會(huì)動(dòng)態(tài)改變尺寸的。
UITableView 的 rowHeight 的值要設(shè)置為 UITableViewAutomaticDimension
和 iOS 7 一樣,可以實(shí)現(xiàn) estimatedHeightForRowAtIndexPath 方法提升 table view 的第一次加載速度。
任何時(shí)候 cell 的 intrinsicContentSize 改變了(比如 table view 的寬度變了),都必須重新加載 table view 以更新 cell。
例子

在 Xcode 中新建一個(gè)項(xiàng)目,在 storyboard 中創(chuàng)建一個(gè) UITableViewController 的 IB,創(chuàng)建一個(gè)如下樣子的 cell:

這個(gè) cell 中有 3 個(gè)元素,其中 imageView 的 autoLayout 約束為:

  • imageView 左邊離 contentView 左邊 0
  • imageView 上邊離 contentView 上邊 0
  • imageView 的 width 和 height 為 80
  • imageView 下邊離 contentView 下邊大于等于 0(為了防止內(nèi)容太少,導(dǎo)致 cell 高度小于圖片高度)

titleLabel 的 autoLayout 約束為:

  • titleLabel 左邊離 imageView 右邊 8
  • titleLabel 上邊和 imageView 上邊在同一只線上
  • titleLabel 右邊離 contentView 右邊 0
  • titleLabel 下邊離 description 上邊 8
  • titleLabel 的高度小于等于 22,優(yōu)先級(jí)為 250

descriptionLabel 的約束為:

  • descriptionLabel 左邊和 titleLabel 左邊在同一直線上
  • descriptionLabel 上邊里 titleLabel 8
  • descriptionLabel 下邊里 contentView 下邊 0
  • descriptionLabel 右邊離 contentView 右邊 0

然后在這個(gè) IB 對(duì)應(yīng)的 UITableViewController 中加載一些數(shù)據(jù)進(jìn)去,顯示效果如圖:

實(shí)現(xiàn)這個(gè)效果,我除了設(shè)置了 autoLayout,還設(shè)置了 tableView 的 rowHeight = UITableViewAutomaticDimension,然后就是這樣了。一點(diǎn)計(jì)算 cell 高度的代碼都沒有?。∥疫B heightForRowAtIndexPath都不用實(shí)現(xiàn),真的是….爽出味??!所以如果已經(jīng)在開發(fā) iOS 8 Only 的應(yīng)用了一定要用autolayout,把煩人的計(jì)算交給 autolayout 去吧。

CollectionView 中的 cell 自適應(yīng)

在 collection view 中也能讓 cell 自適應(yīng)內(nèi)容大小,如果 UICollectionView 的 layout 是一個(gè) UICollectionViewFlowLayout,只需要將 layout.itemSize = ... 改成 layout.estimatedItemSize = ...。 只要設(shè)置了 layout 的 estimatedItemSize,collection view 就會(huì)根據(jù) cell 里面的 autolayout 約束去確定cell 的大小。

原理:

  • collection view 根據(jù) layout 的 estimatedItemSize 算出估計(jì)的 contentSize,有了 contentSize collection view 就開始顯示
  • collection view 在顯示的過程中,即將被顯示的 cell 根據(jù) autolayout 的約束算出自適應(yīng)內(nèi)容的 size
  • layout 從 collection view 里獲取更新過的 size attribute
  • layout 返回最終的 size attribute 給 collection view
  • collection 使用這個(gè)最終的 size attribute 展示 cell

UITextView 輸入內(nèi)容實(shí)時(shí)更新 cell 的高度
在一個(gè)動(dòng)態(tài)數(shù)據(jù)的 table view 中,cell 根據(jù) text view 內(nèi)容的輸入實(shí)時(shí)改變 cell 和 table view 的高度。自動(dòng)計(jì)算 cell 高度的功能使用 iOS 8 才支持的自適應(yīng) cell,先上圖,我們最終要實(shí)現(xiàn)的效果是這樣的:

實(shí)現(xiàn)上面效果的基本原理是:

  • 在 cell 中設(shè)置好 text view 的 autolayout,讓 cell 可以根據(jù)內(nèi)容自適應(yīng)大小
  • text view 中輸入內(nèi)容,根據(jù)內(nèi)容更新 textView 的高度
  • 調(diào)用 tableView 的 beginUpdates 和 endUpdates,重新計(jì)算 cell 的高度

將 text view 更新后的數(shù)據(jù)保存,以免 table view 滾動(dòng)超過一屏再滾回來 text view 中的數(shù)據(jù)又不刷新成原來的數(shù)據(jù)了。

功能具體實(shí)現(xiàn)方法:

新建一個(gè)項(xiàng)目,拉出 TableViewController,在 cell 上添加一個(gè) UITextView。

首先設(shè)置 text view 的 autolayout,比較關(guān)鍵的 constraint 是要設(shè)置 textView 的高度大于等于一個(gè)值。如圖:

然后,設(shè)置 UITextView 的 scrollEnable 為 NO。這一點(diǎn)很關(guān)鍵,如果不設(shè)置為 NO,UITextView 在內(nèi)容超出 frame 后,重新設(shè)置 text view 的高度會(huì)失效,并出現(xiàn)滾動(dòng)條。

根據(jù)剛才在 storyboard 中創(chuàng)建的 cell,新建一個(gè) UITableViewCell 類。

復(fù)制代碼 代碼如下:

#import <UIKit/UIKit.h>

@interface TextViewCell : UITableViewCell

@property (weak, nonatomic) IBOutlet UITextView *textView;

@end


創(chuàng)建 TableViewController 并初始化一些數(shù)據(jù)
復(fù)制代碼 代碼如下:

#import "TableViewController.h"
#import "TextViewCell.h"

@interface TableViewController ()

@property (nonatomic, strong) NSArray *data;

@end


復(fù)制代碼 代碼如下:

@implementation TableViewController

- (void)viewDidLoad {
  [super viewDidLoad];

  //  支持自適應(yīng) cell
  self.tableView.estimatedRowHeight = 100;
  self.tableView.rowHeight = UITableViewAutomaticDimension;

  self.data = @[@"Cell 1 ", @"Cell 2", @"Cell 3", @"Cell 4", @"Cell 5", @"Cell 6", @"Cell 7", @"Cell 8"];
}

#pragma mark - Table view data source

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

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  TextViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"TextViewCell" forIndexPath:indexPath];
  cell.textView.text = self.data[indexPath.row];
  return cell;
}


使用上面的代碼項(xiàng)目已經(jīng)可以運(yùn)行了,但是 text view 還不能自動(dòng)更新大小,下面來實(shí)現(xiàn) text view 根據(jù)內(nèi)容計(jì)算高度

先在 storyboard 中,將 UITextView 的 delegate 設(shè)置為 cell

在 TextViewCell.m 中實(shí)現(xiàn) - (void)textViewDidChange:(UITextView *)textView,每次 text view 內(nèi)容改變的時(shí)候,就重新計(jì)算一次 text view 的大小,并讓 table view 更新高度。

復(fù)制代碼 代碼如下:

#import "TextViewCell.h"

@implementation TextViewCell

- (void)textViewDidChange:(UITextView *)textView
{
  CGRect bounds = textView.bounds;

  // 計(jì)算 text view 的高度
  CGSize maxSize = CGSizeMake(bounds.size.width, CGFLOAT_MAX);
  CGSize newSize = [textView sizeThatFits:maxSize];
  bounds.size = newSize;

  textView.bounds = bounds;

  // 讓 table view 重新計(jì)算高度
  UITableView *tableView = [self tableView];
  [tableView beginUpdates];
  [tableView endUpdates];
}

- (UITableView *)tableView
{
  UIView *tableView = self.superview;

  while (![tableView isKindOfClass:[UITableView class]] && tableView) {
    tableView = tableView.superview;
  }

  return (UITableView *)tableView;
}

@end


這樣就已經(jīng)實(shí)現(xiàn)了 text view 改變內(nèi)容自動(dòng)更新 cell 高度的功能,這篇文章沒有涉及到計(jì)算 cell 高度的代碼,因?yàn)橛?jì)算 cell 高度的工作全部交給 iOS 8 的 autolayout 自動(dòng)計(jì)算了,這讓我們少寫了許多令人痛苦的代碼。

最后:為了防止 table view 過長(zhǎng),導(dǎo)致滾動(dòng)后重新加載 cell,會(huì)讓 text view 中的內(nèi)容還原的問題,我們應(yīng)該在更新了 text view 的內(nèi)容之后保存數(shù)據(jù)。(如果是在編輯狀態(tài)下,還需要考慮取消編輯后的回滾功能。 普通數(shù)組數(shù)據(jù),可以保存一個(gè)原始數(shù)據(jù)的副本,如果用戶取消編輯,就設(shè)置 data 為原始數(shù)據(jù)的副本。如果是 NSManagedObject 對(duì)象可以使用 NSUndoManage,不過這些已經(jīng)超出本篇文章的內(nèi)容范圍了。)

為了在 text view 更新后能讓 TableViewController 中的 data 更新,需要為 cell 添加一個(gè) delegate,在 text view 更新后調(diào)用 delegate,TableViewController 中收到 delegate 信息后更新 data。

修改后的 TextViewCell.h

復(fù)制代碼 代碼如下:

#import <UIKit/UIKit.h>

@protocol TextViewCellDelegate;

@interface TextViewCell : UITableViewCell

@property (weak, nonatomic) IBOutlet UITextView *textView;

@property (weak, nonatomic) id<TextViewCellDelegate> delegate;

@end


復(fù)制代碼 代碼如下:

@protocol TextViewCellDelegate <NSObject>

- (void)textViewCell:(TextViewCell *)cell didChangeText:(NSString *)text;

@end


在 TextView.m的 - (void)textViewDidChange:(UITextView *)textView 中添加 delegate 的調(diào)用
復(fù)制代碼 代碼如下:

- (void)textViewDidChange:(UITextView *)textView
{
  if ([self.delegate respondsToSelector:@selector(textViewCell:didChangeText:)]) {
    [self.delegate textViewCell:self didChangeText:textView.text];
  }

  // 計(jì)算 text view 的高度
  ...
  // 讓 table view 重新計(jì)算高度
  ...
}


最后在 TableViewController.m 的最后實(shí)現(xiàn) TextViewCellDelegate 的方法,更新 data
復(fù)制代碼 代碼如下:

#pragma mark - TextViewCellDelegate

- (void)textViewCell:(TextViewCell *)cell didChangeText:(NSString *)text
{
  NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];

  NSMutableArray *data = [self.data mutableCopy];
  data[indexPath.row] = text;
  self.data = [data copy];
}

相關(guān)文章

最新評(píng)論