如何在表视图中的UITableViewCells中使用“自动布局”,让每个单元格的内容和子视图确定行高度(自身/自动),同时保持平滑的滚动性能?
当前回答
可变高度UITableViewCell的快速示例
为Swift 3更新
胡威廉(William Hu)的快速回答很好,但在我第一次学习做某事时,它能帮助我掌握一些简单而详细的步骤。下面的示例是我在学习使用可变单元格高度制作UITableView时的测试项目。我基于Swift的这个基本UITableView示例。
完成的项目应如下所示:
创建新项目
它可以只是一个单视图应用程序。
添加代码
将新的Swift文件添加到项目中。将其命名为MyCustomCell。此类将保存添加到情节提要中单元格的视图的出口。在这个基本示例中,每个单元格中只有一个标签。
import UIKit
class MyCustomCell: UITableViewCell {
@IBOutlet weak var myCellLabel: UILabel!
}
我们稍后将连接此插座。
打开ViewController.swift并确保您具有以下内容:
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
// These strings will be the data for the table view cells
let animals: [String] = [
"Ten horses: horse horse horse horse horse horse horse horse horse horse ",
"Three cows: cow, cow, cow",
"One camel: camel",
"Ninety-nine sheep: sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep baaaa sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep",
"Thirty goats: goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat "]
// Don't forget to enter this in IB also
let cellReuseIdentifier = "cell"
@IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// delegate and data source
tableView.delegate = self
tableView.dataSource = self
// Along with auto layout, these are the keys for enabling variable cell height
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
}
// number of rows in table view
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.animals.count
}
// create a cell for each table view row
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:MyCustomCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! MyCustomCell
cell.myCellLabel.text = self.animals[indexPath.row]
return cell
}
// method to run when table view cell is tapped
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("You tapped cell number \(indexPath.row).")
}
}
重要说明:
以下两行代码(以及自动布局)使可变单元格高度成为可能:tableView.estimatedRowHeight=44.0tableView.rowHeight=UITableViewAutomaticDimension
设置情节提要
将表视图添加到视图控制器中,并使用自动布局将其固定到四边。然后将表视图单元格拖到表视图上。然后将标签拖到原型单元格上。使用自动布局将标签固定到表视图单元的内容视图的四个边缘。
重要说明:
自动布局与我上面提到的两行重要代码一起工作。如果不使用自动布局,它将无法工作。
其他IB设置
自定义类名和标识符
选择TableViewCell并将自定义类设置为MyCustomCell(我们添加的Swift文件中的类名称)。还将Identifier设置为cell(与上面代码中用于cellReuseIdentifier的字符串相同)。
标签的零行
将标签中的行数设置为0。这意味着多行,并允许标签根据其内容调整自身大小。
连接插座
控件从情节提要中的表视图拖动到ViewController代码中的tableView变量。对Prototype单元格中的Label和MyCustomCell类中的myCellLabel变量执行同样的操作。
完成了
您现在应该能够运行项目并获得高度可变的单元格。
笔记
此示例仅适用于iOS 8及更高版本。如果您仍然需要支持iOS 7,那么这将不适用于您。未来项目中您自己的自定义单元格可能不止一个标签。确保将所有内容固定正确,以便自动布局可以确定要使用的正确高度。你可能还需要使用垂直抗压和拥抱。有关这方面的更多信息,请参阅本文。如果不固定前缘和后缘(左侧和右侧),则可能还需要设置标签的preferredMaxLayoutWidth,以便它知道何时换行。例如,如果在上面的项目中向标签添加了“水平居中”约束,而不是固定前缘和后缘,则需要将此行添加到tableView:cellForRowAtIndexPath方法:cell.myCellLabel.referencedMaxLayoutWidth=表格视图边界宽度
另请参见
了解iOS 8中的自调整单元格和动态类型具有不同行高的表视图单元格Swift的UITableView示例
其他回答
像@Bob Spryn一样,我遇到了一个非常重要的问题,我把这个问题作为答案发布。
我纠结于@smileyborg的回答了一会儿。我遇到的问题是,如果您在IB中使用其他元素(UILabels、UIButtons等)定义了原型单元,当您使用[[YourTableViewCellClass alloc]init]实例化该单元时,它不会实例化该单元中的所有其他元素,除非您编写了这样做的代码。(我对initWithStyle有过类似的经历。)
要让故事板实例化所有其他元素,请使用[tableView dequeueReusableCellWithIdentifier:@“DoseNeed”](不是[tableView de queueReusable CellWithIdentifier:forIndexPath:],因为这会引起有趣的问题。)执行此操作时,将实例化IB中定义的所有元素。
我将@smileyborg的iOS7解决方案归入一个类别
我决定用@smileyborg将这个聪明的解决方案包装成UICollectionViewCell+AutoLayoutDynamicHeightCalculation类别。
该类别还纠正了@wildsmonkey的回答中概述的问题(从笔尖和systemLayoutSizeFittingSize加载单元格:返回CGRectZero)
它不考虑任何缓存,但现在适合我的需要。随意复制、粘贴和破解。
UI集合视图单元格+自动布局动态高度计算.h
#import <UIKit/UIKit.h>
typedef void (^UICollectionViewCellAutoLayoutRenderBlock)(void);
/**
* A category on UICollectionViewCell to aid calculating dynamic heights based on AutoLayout contraints.
*
* Many thanks to @smileyborg and @wildmonkey
*
* @see stackoverflow.com/questions/18746929/using-auto-layout-in-uitableview-for-dynamic-cell-layouts-variable-row-heights
*/
@interface UICollectionViewCell (AutoLayoutDynamicHeightCalculation)
/**
* Grab an instance of the receiving type to use in order to calculate AutoLayout contraint driven dynamic height. The method pulls the cell from a nib file and moves any Interface Builder defined contrainsts to the content view.
*
* @param name Name of the nib file.
*
* @return collection view cell for using to calculate content based height
*/
+ (instancetype)heightCalculationCellFromNibWithName:(NSString *)name;
/**
* Returns the height of the receiver after rendering with your model data and applying an AutoLayout pass
*
* @param block Render the model data to your UI elements in this block
*
* @return Calculated constraint derived height
*/
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width;
/**
* Directly calls `heightAfterAutoLayoutPassAndRenderingWithBlock:collectionViewWidth` assuming a collection view width spanning the [UIScreen mainScreen] bounds
*/
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block;
@end
UI集合视图单元格+自动布局动态高度计算.m
#import "UICollectionViewCell+AutoLayout.h"
@implementation UICollectionViewCell (AutoLayout)
#pragma mark Dummy Cell Generator
+ (instancetype)heightCalculationCellFromNibWithName:(NSString *)name
{
UICollectionViewCell *heightCalculationCell = [[[NSBundle mainBundle] loadNibNamed:name owner:self options:nil] lastObject];
[heightCalculationCell moveInterfaceBuilderLayoutConstraintsToContentView];
return heightCalculationCell;
}
#pragma mark Moving Constraints
- (void)moveInterfaceBuilderLayoutConstraintsToContentView
{
[self.constraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *constraint, NSUInteger idx, BOOL *stop) {
[self removeConstraint:constraint];
id firstItem = constraint.firstItem == self ? self.contentView : constraint.firstItem;
id secondItem = constraint.secondItem == self ? self.contentView : constraint.secondItem;
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:firstItem
attribute:constraint.firstAttribute
relatedBy:constraint.relation
toItem:secondItem
attribute:constraint.secondAttribute
multiplier:constraint.multiplier
constant:constraint.constant]];
}];
}
#pragma mark Height
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block
{
return [self heightAfterAutoLayoutPassAndRenderingWithBlock:block
collectionViewWidth:CGRectGetWidth([[UIScreen mainScreen] bounds])];
}
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width
{
NSParameterAssert(block);
block();
[self setNeedsUpdateConstraints];
[self updateConstraintsIfNeeded];
self.bounds = CGRectMake(0.0f, 0.0f, width, CGRectGetHeight(self.bounds));
[self setNeedsLayout];
[self layoutIfNeeded];
CGSize calculatedSize = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return calculatedSize.height;
}
@end
用法示例:
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
MYSweetCell *cell = [MYSweetCell heightCalculationCellFromNibWithName:NSStringFromClass([MYSweetCell class])];
CGFloat height = [cell heightAfterAutoLayoutPassAndRenderingWithBlock:^{
[(id<MYSweetCellRenderProtocol>)cell renderWithModel:someModel];
}];
return CGSizeMake(CGRectGetWidth(self.collectionView.bounds), height);
}
谢天谢地,我们不必在iOS8中演奏爵士乐,但现在就可以了!
如果您以编程方式进行布局,以下是在Swift中使用锚的iOS 10需要考虑的问题。
有三个规则/步骤
数字1:在viewDidLoad上设置tableview的这两个财产,第一个属性告诉表视图应该期望其单元格的动态大小,第二个属性只是让应用程序计算滚动条指示器的大小,这样有助于提高性能。
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 100
第二:这一点很重要。您需要将子视图添加到单元格的contentView中,而不是添加到视图中,还需要使用其layoutsmargingguide将子视图锚定到顶部和底部,这是一个很好的示例。
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setUpViews()
}
private func setUpViews() {
contentView.addSubview(movieImageView)
contentView.addSubview(descriptionLabel)
let marginGuide = contentView.layoutMarginsGuide
NSLayoutConstraint.activate([
movieImageView.heightAnchor.constraint(equalToConstant: 80),
movieImageView.widthAnchor.constraint(equalToConstant: 80),
movieImageView.leftAnchor.constraint(equalTo: marginGuide.leftAnchor),
movieImageView.topAnchor.constraint(equalTo: marginGuide.topAnchor, constant: 20),
descriptionLabel.leftAnchor.constraint(equalTo: movieImageView.rightAnchor, constant: 15),
descriptionLabel.rightAnchor.constraint(equalTo: marginGuide.rightAnchor),
descriptionLabel.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor, constant: -15),
descriptionLabel.topAnchor.constraint(equalTo: movieImageView.topAnchor)
])
}
创建一个将添加子视图并执行布局的方法,在init方法中调用它。
第3:不要调用方法:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
}
如果你这样做,你将覆盖你的实现。
对于表视图中的动态单元格,请遵循以下3条规则。
这是一个有效的实现https://github.com/jamesrochabrun/MinimalViewController
我必须使用动态视图(通过代码设置视图和约束),当我想设置preferredMaxLayoutWidth标签的宽度为0时。所以我有错误的细胞高度。
然后我添加了
[cell layoutSubviews];
执行之前
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
之后,标签的宽度符合预期,动态高度计算正确。
关于@smileyborg接受的答案,我发现
[cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]
在某些约束不明确的情况下是不可靠的。最好使用下面UIView上的帮助器类别强制布局引擎计算一个方向上的高度:
-(CGFloat)systemLayoutHeightForWidth:(CGFloat)w{
[self setNeedsLayout];
[self layoutIfNeeded];
CGSize size = [self systemLayoutSizeFittingSize:CGSizeMake(w, 1) withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
CGFloat h = size.height;
return h;
}
其中w:是表视图的宽度
推荐文章
- 如何删除默认的导航栏空间在SwiftUI导航视图
- 如何在iOS中使用Swift编程segue
- Swift -整数转换为小时/分钟/秒
- Swift:声明一个空字典
- 在成功提交我的应用程序后,“太多符号文件”
- 首先添加一个UIView,甚至是导航栏
- 我如何改变UIButton标题颜色?
- 在Swift中如何调用GCD主线程上的参数方法?
- NSLayoutConstraints是可动画的吗?
- iOS -构建失败,CocoaPods无法找到头文件
- CFNetwork SSLHandshake iOS 9失败
- 请求失败:不可接受的内容类型:文本/html使用AFNetworking 2.0
- 缺少推荐的图标文件-该包不包含iPhone / iPod Touch的应用程序图标,像素为“120x120”,png格式
- 以编程方式创建segue
- 如何使用Xcode创建。ipa文件?