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。