如何在表视图中的UITableViewCells中使用“自动布局”,让每个单元格的内容和子视图确定行高度(自身/自动),同时保持平滑的滚动性能?
当前回答
(对于底部读取的Xcode 8.x/Xcode 9.x)
注意Xcode 7.x中的以下问题,这可能是混淆的根源:
Interface Builder无法正确处理自动调整单元格大小的设置。即使你的约束绝对有效,IB仍然会抱怨,并给你令人困惑的建议和错误。原因是IB不愿意根据您的约束条件改变行的高度(以便单元格适合您的内容)。相反,它会保持行的高度不变,并开始建议您更改约束,您应该忽略这些约束。
例如,假设您已经设置好了一切,没有警告,没有错误,一切正常。
现在,如果您更改字体大小(在本例中,我将描述标签的字体大小从17.0更改为18.0)。
由于字体大小增加,标签现在希望占据3行(之前它占据2行)。
如果Interface Builder按预期工作,它将调整单元格的高度以适应新的标签高度。然而,实际发生的情况是IB显示红色的自动布局错误图标,并建议您修改拥抱/压缩优先级。
您应该忽略这些警告。相反,您可以*手动更改行的高度(选择“单元格”>“大小检查器”>“行高度”)。
我一次单击一次(使用上/下步进器)改变这个高度,直到红色箭头错误消失!(实际上,您会收到黄色警告,此时只需继续并执行“更新帧”,一切都应该正常)。
*请注意,您实际上不必在Interface Builder中解决这些红色错误或黄色警告-在运行时,一切都会正常工作(即使IB显示错误/警告)。只需确保在运行时控制台日志中没有出现任何自动布局错误。事实上,尝试总是更新IB中的行高度是非常令人讨厌的,有时几乎不可能(因为分数值)。为了防止烦人的IB警告/错误,您可以选择所涉及的视图,并在Size Inspector中为属性Ambiguity选择Verify Position Only
Xcode 8.x/Xcode 9.x看起来(有时)做的事情与Xcode 7.x不同,但仍然不正确。例如,即使抗压优先级/拥抱优先级设置为必需(1000),Interface Builder也可能拉伸或剪裁标签以适合单元格(而不是调整单元格高度以适合标签周围)。在这种情况下,它甚至可能不会显示任何自动布局警告或错误。或者,有时它完全像上面描述的Xcode7.x那样。
其他回答
我将@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中演奏爵士乐,但现在就可以了!
以下是我的解决方案:
在TableView加载视图之前,您需要告诉它估计的高度。否则它将无法像预期的那样运行。
目标-C
- (void)viewWillAppear:(BOOL)animated {
_messageField.delegate = self;
_tableView.estimatedRowHeight = 65.0;
_tableView.rowHeight = UITableViewAutomaticDimension;
}
更新至Swift 4.2
override func viewWillAppear(_ animated: Bool) {
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 65.0
}
只需在视图控制器中添加这两个功能,即可解决问题。这里,列表是一个字符串数组,其中包含每行的字符串。
func tableView(_ tableView: UITableView,
estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
tableView.rowHeight = self.calculateHeight(inString: list[indexPath.row])
return (tableView.rowHeight)
}
func calculateHeight(inString:String) -> CGFloat
{
let messageString = input.text
let attributes : [NSAttributedStringKey : Any] = [NSAttributedStringKey(rawValue: NSAttributedStringKey.font.rawValue) : UIFont.systemFont(ofSize: 15.0)]
let attributedString : NSAttributedString = NSAttributedString(string: messageString!, attributes: attributes)
let rect : CGRect = attributedString.boundingRect(with: CGSize(width: 222.0, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, context: nil)
let requredSize:CGRect = rect
return requredSize.height
}
以防人们仍然对此感到困扰。我写了一篇关于在UITableViews中使用Autolayout的快速博客文章,利用Autolayout For Dynamic Cell Heights以及一个开源组件来帮助实现这一点,使其更加抽象和易于实现。https://github.com/Raizlabs/RZCellSizeManager
如果单元格高度根据内容是动态的,则应精确计算出它,然后在渲染单元格之前返回高度值。一种简单的方法是在表视图单元格代码中定义计数方法,以便控制器在表单元格高度委托方法处调用。如果高度取决于表格或屏幕的宽度,请不要忘记计算实际单元格框架宽度(默认值为320)。也就是说,在表单元格高度委托方法中,首先使用cell.frame来校正单元格宽度,然后调用单元格中定义的计数高度方法来获得合适的值并返回它。
PS。可以在另一个方法中定义用于生成单元格对象的代码,以便调用不同的表视图单元格委托方法。
推荐文章
- 如何删除默认的导航栏空间在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文件?