Fantageek翻译系列之《使用Autolayout显示变化高度的UITableViewCell》

这篇博客主要在于,解释如何通过仅仅使用Autolayout很很少的代码,显示高度不同的Cell。虽然标题说的是TableView,但是CollectionView同样适合。但是,这种方法只使用iOS7和iOS8。

在Github上的实例代码是DynamicTableViewCellHeight

这个Demo显示了一些名人名言,他看起来像这样:

preferredMaxLayoutWidth

这种方法,主要来自于preferredMaxLayoutWidth属性。对于更多高级用法,请看Auto Layout and Views that Wrap

User Interface

我使用的是Storyboard, 但是你能够使用xib或者代码, 对于如何使用代码,你可以看一下这篇文章AutoSize UITableViewCell height programmatically

这里,“引言Label”显示多行(通过设置numberOfLines属性为0)。因为我们有2个Label,AutoLayout不知道如何扩大,不知道cell的大小改变时哪一个Label保持大小不变。在这种情况下,我想要“引言Label”扩大,所以减少减少垂直方向Hugging优先级,并且增加保持自身大小不变优先级。

关于hugging和resistance优先级的区别,可以看这篇文章Cocoa Autolayout: content hugging vs content compression resistance priority

注意:

1、Dynamic Table View Cell Height and Auto Layout,这篇博客告诉我们,我们应该给labels的“intrinsic content”属性到1000,并且设置Intrinsic Size为占位符大小。我认为这是不需要的。

2、你必须给TableViewCell的contentView设置属性。

3、对于在Interface Builder中的CollectionViewCell。你不会看到contentView,但是你真的是和contentView打交道。

Cell

QuoteTableViewCell.h

1 @interface QuoteTableViewCell : UITableViewCell
2 @property (weak, nonatomic) IBOutlet UILabel *numberLabel;
3 @property (weak, nonatomic) IBOutlet UILabel *quoteLabel;
4  
5 @end

QuoteTableViewCell.m

 1 @implementation QuoteTableViewCell
 2  
 3 // (1)
 4 - (void)setBounds:(CGRect)bounds
 5 {
 6     [super setBounds:bounds];
 7  
 8     self.contentView.frame = self.bounds;
 9 }
10  
11 - (void)layoutSubviews
12 {
13     [super layoutSubviews];
14  
15     // (2)
16     [self.contentView updateConstraintsIfNeeded];
17     [self.contentView layoutIfNeeded];
18  
19     // (3)
20     self.quoteLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.quoteLabel.frame);
21 }
22  
23 @end

ViewController

ViewController.m

  1 #define SYSTEM_VERSION                              ([[UIDevice currentDevice] systemVersion])
  2 #define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v)  ([SYSTEM_VERSION compare:v options:NSNumericSearch] != NSOrderedAscending)
  3 #define IS_IOS8_OR_ABOVE                            (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0"))
  4  
  5 @interface ViewController () <UITableViewDataSource, UITableViewDelegate>
  6  
  7 @property (weak, nonatomic) IBOutlet UITableView *tableView;
  8 @property (nonatomic, strong) NSArray *items;
  9 @property (nonatomic, strong) QuoteTableViewCell *prototypeCell;
 10  
 11 @end
 12  
 13 @implementation ViewController
 14  
 15 - (void)viewDidLoad {
 16     [super viewDidLoad];
 17  
 18     [self setupTableView];
 19     [self loadData];
 20 }
 21  
 22 - (void)didReceiveMemoryWarning {
 23     [super didReceiveMemoryWarning];
 24     // Dispose of any resources that can be recreated.
 25 }
 26  
 27 #pragma mark - Setup
 28 - (void)setupTableView
 29 {
 30     self.tableView.dataSource = self;
 31     self.tableView.delegate = self;
 32 }
 33  
 34 #pragma mark - Data
 35 - (void)loadData
 36 {
 37     NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"quotes" ofType:@"plist"];
 38     self.items = [[NSArray alloc] initWithContentsOfFile:plistPath];
 39  
 40     [self.tableView reloadData];
 41 }
 42  
 43 #pragma mark - PrototypeCell
 44 // (4)
 45 - (QuoteTableViewCell *)prototypeCell
 46 {
 47     if (!_prototypeCell) {
 48         _prototypeCell = [self.tableView dequeueReusableCellWithIdentifier:NSStringFromClass([QuoteTableViewCell class])];
 49     }
 50  
 51     return _prototypeCell;
 52 }
 53  
 54 #pragma mark - Configure
 55 - (void)configureCell:(QuoteTableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
 56 {
 57     NSString *quote = self.items[indexPath.row];
 58  
 59     cell.numberLabel.text = [NSString stringWithFormat:@"Quote %ld", (long)indexPath.row];
 60     cell.quoteLabel.text = quote;
 61 }
 62  
 63 #pragma mark - UITableViewDataSouce
 64 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
 65 {
 66     return 1;
 67 }
 68  
 69 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
 70 {
 71     return self.items.count;
 72 }
 73  
 74 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
 75 {
 76     QuoteTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([QuoteTableViewCell class])];
 77  
 78     [self configureCell:cell forRowAtIndexPath:indexPath];
 79  
 80     return cell;
 81 }
 82  
 83 #pragma mark - UITableViewDelegate
 84 // (5)
 85 - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
 86 {
 87     return UITableViewAutomaticDimension;
 88 }
 89  
 90 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
 91 {
 92     // (6)
 93     if (IS_IOS8_OR_ABOVE) {
 94         return UITableViewAutomaticDimension;
 95     }
 96  
 97     // (7)
 98     //self.prototypeCell.bounds = CGRectMake(0, 0, CGRectGetWidth(self.tableView.bounds), CGRectGetHeight(self.prototypeCell.bounds));
 99  
100     [self configureCell:self.prototypeCell forRowAtIndexPath:indexPath];
101  
102     // (8)
103     [self.prototypeCell updateConstraintsIfNeeded];
104     [self.prototypeCell layoutIfNeeded];
105  
106     // (9)
107     return [self.prototypeCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
108  
109 }
110  
111 @end

注意以下几点:

1、Auto Layout in UICollectionViewCell not working

2、AutoSize UITableViewCell height programmatically

Make sure the contentView does a layout pass here so that its subviews have their frames set, which we need to use to set the preferredMaxLayoutWidth below.

Set the preferredMaxLayoutWidth of the mutli-line bodyLabel based on the evaluated width of the label’s frame, as this will allow the text to wrap correctly, and as a result allow the label to take on the correct height.

3、你只需要调用[self.contentView layoutIfNeeded]。

4、如果你要改变某些限制,你需要调用[self.contentView updateConstraintsIfNeeded]。

5、如果你调用[self.contentView updateConstraintsIfNeeded],你必须在之前调用[self.contentView layoutIfNeeded]。

6、不需要调用[self.contentView setsNeedLayout],或者self.contentView setsNeedUpdateConstraints]。

ViewController

  1 #define SYSTEM_VERSION                              ([[UIDevice currentDevice] systemVersion])
  2 #define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v)  ([SYSTEM_VERSION compare:v options:NSNumericSearch] != NSOrderedAscending)
  3 #define IS_IOS8_OR_ABOVE                            (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0"))
  4  
  5 @interface ViewController () <UITableViewDataSource, UITableViewDelegate>
  6  
  7 @property (weak, nonatomic) IBOutlet UITableView *tableView;
  8 @property (nonatomic, strong) NSArray *items;
  9 @property (nonatomic, strong) QuoteTableViewCell *prototypeCell;
 10  
 11 @end
 12  
 13 @implementation ViewController
 14  
 15 - (void)viewDidLoad {
 16     [super viewDidLoad];
 17  
 18     [self setupTableView];
 19     [self loadData];
 20 }
 21  
 22 - (void)didReceiveMemoryWarning {
 23     [super didReceiveMemoryWarning];
 24     // Dispose of any resources that can be recreated.
 25 }
 26  
 27 #pragma mark - Setup
 28 - (void)setupTableView
 29 {
 30     self.tableView.dataSource = self;
 31     self.tableView.delegate = self;
 32 }
 33  
 34 #pragma mark - Data
 35 - (void)loadData
 36 {
 37     NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"quotes" ofType:@"plist"];
 38     self.items = [[NSArray alloc] initWithContentsOfFile:plistPath];
 39  
 40     [self.tableView reloadData];
 41 }
 42  
 43 #pragma mark - PrototypeCell
 44 // (4)
 45 - (QuoteTableViewCell *)prototypeCell
 46 {
 47     if (!_prototypeCell) {
 48         _prototypeCell = [self.tableView dequeueReusableCellWithIdentifier:NSStringFromClass([QuoteTableViewCell class])];
 49     }
 50  
 51     return _prototypeCell;
 52 }
 53  
 54 #pragma mark - Configure
 55 - (void)configureCell:(QuoteTableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
 56 {
 57     NSString *quote = self.items[indexPath.row];
 58  
 59     cell.numberLabel.text = [NSString stringWithFormat:@"Quote %ld", (long)indexPath.row];
 60     cell.quoteLabel.text = quote;
 61 }
 62  
 63 #pragma mark - UITableViewDataSouce
 64 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
 65 {
 66     return 1;
 67 }
 68  
 69 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
 70 {
 71     return self.items.count;
 72 }
 73  
 74 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
 75 {
 76     QuoteTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([QuoteTableViewCell class])];
 77  
 78     [self configureCell:cell forRowAtIndexPath:indexPath];
 79  
 80     return cell;
 81 }
 82  
 83 #pragma mark - UITableViewDelegate
 84 // (5)
 85 - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
 86 {
 87     return UITableViewAutomaticDimension;
 88 }
 89  
 90 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
 91 {
 92     // (6)
 93     if (IS_IOS8_OR_ABOVE) {
 94         return UITableViewAutomaticDimension;
 95     }
 96  
 97     // (7)
 98     //self.prototypeCell.bounds = CGRectMake(0, 0, CGRectGetWidth(self.tableView.bounds), CGRectGetHeight(self.prototypeCell.bounds));
 99  
100     [self configureCell:self.prototypeCell forRowAtIndexPath:indexPath];
101  
102     // (8)
103     [self.prototypeCell updateConstraintsIfNeeded];
104     [self.prototypeCell layoutIfNeeded];
105  
106     // (9)
107     return [self.prototypeCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
108  
109 }
110  
111 @end

7、原型cell绝不会显示出来,它用来布局一个cell并且决定一个需要的高度。

8、你能够使用UITableViewAutomaticDimension,或者使用一个大约合理的高度。

9、iOS8 autoSizing属性需要使用UITableViewAutomaticDimension。

 

posted @ 2015-01-11 16:37  PI_WF  阅读(783)  评论(0编辑  收藏  举报