如何在表视图中的UITableViewCells中使用“自动布局”,让每个单元格的内容和子视图确定行高度(自身/自动),同时保持平滑的滚动性能?


当前回答

我只是对rowHeight和estimatedRowHeight的两个值进行了一些愚蠢的尝试和错误,并认为它可能会提供一些调试细节:

如果同时设置它们,或者仅设置估计的RowHeight,则将获得所需的行为:

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 1.00001 // MUST be greater than 1

建议您尽最大努力得到正确的估计,但最终结果并无不同。这只会影响你的表现。


如果只设置rowHeight,即只执行以下操作:

tableView.rowHeight = UITableViewAutomaticDimension

您的最终结果不会如预期:


如果将estimatedRowHeight设置为1或更小,则无论rowHeight如何,都会崩溃。

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 1 

我崩溃了,出现以下错误消息:

Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'table view row height
must not be negative - provided height for index path (<NSIndexPath:
0xc000000000000016> {length = 2, path = 0 - 0}) is -1.000000'
    ...some other lines...

libc++abi.dylib: terminating with uncaught exception of type
NSException

其他回答

Swift中的另一个iOs7+iOs8解决方案

var cell2height:CGFloat=44

override func viewDidLoad() {
    super.viewDidLoad()
    theTable.rowHeight = UITableViewAutomaticDimension
    theTable.estimatedRowHeight = 44.0;
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell =  tableView.dequeueReusableCellWithIdentifier("myTableViewCell", forIndexPath: indexPath) as! myTableViewCell
    cell2height=cell.contentView.height
    return cell
}

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    if #available(iOS 8.0, *) {
        return UITableViewAutomaticDimension
    } else {
        return cell2height
    }
}

以防人们仍然对此感到困扰。我写了一篇关于在UITableViews中使用Autolayout的快速博客文章,利用Autolayout For Dynamic Cell Heights以及一个开源组件来帮助实现这一点,使其更加抽象和易于实现。https://github.com/Raizlabs/RZCellSizeManager

在我的例子中,我必须创建一个自定义单元格,其中包含来自服务器的图像,可以是任何宽度和高度。以及两个具有动态大小(宽度和高度)的UILabels

我在回答自动布局和编程实现了同样的效果:

基本上在@smileyBorg以上的回答有所帮助,但systemLayoutSizeFittingSize从未对我奏效,在我的方法中:

1.不使用自动行高计算属性。2.不使用估计高度3.不需要不必要的更新约束。4.不使用自动首选最大布局宽度。5.不使用systemLayoutSizeFittingSize(应该使用但不适用于我,我不知道它在内部做什么),而是使用我的方法-(float)getViewHeight,我知道它在在内部做些什么。

当我使用几种不同的方式显示单元格时,UITableView单元格中的高度是否可能不同?

@smileyborg提出的解决方案几乎是完美的。如果您有一个自定义单元格,并且需要一个或多个具有动态高度的UILabel,则systemLayoutSizeFittingSize方法与启用自动布局相结合将返回一个CGSizeZero,除非您将所有单元格约束从单元格移到其contentView(如@TomSwift在这里建议的那样,如何调整超视图的大小以适应具有自动布局的所有子视图?)。

为此,您需要在自定义UITableViewCell实现中插入以下代码(感谢@Adrian)。

- (void)awakeFromNib{
    [super awakeFromNib];
    for (NSLayoutConstraint *cellConstraint in self.constraints) {
        [self removeConstraint:cellConstraint];
        id firstItem = cellConstraint.firstItem == self ? self.contentView : cellConstraint.firstItem;
        id seccondItem = cellConstraint.secondItem == self ? self.contentView : cellConstraint.secondItem;
        NSLayoutConstraint *contentViewConstraint =
        [NSLayoutConstraint constraintWithItem:firstItem
                                 attribute:cellConstraint.firstAttribute
                                 relatedBy:cellConstraint.relation
                                    toItem:seccondItem
                                 attribute:cellConstraint.secondAttribute
                                multiplier:cellConstraint.multiplier
                                  constant:cellConstraint.constant];
        [self.contentView addConstraint:contentViewConstraint];
    }
}

将@smileyborg答案与此混合应该会奏效。

另一个“解决方案”:跳过所有这些挫折,改用UIScrollView来获得与UITableView看起来和感觉相同的结果。

对我来说,这是一个痛苦的“解决方案”,因为我花了20多个令人沮丧的小时试图构建类似smileyborg建议的东西,但在几个月和三个版本的应用商店发布中失败了。

我的看法是,如果你真的需要iOS 7支持(对我们来说,这是必不可少的),那么这项技术太脆弱了,你会竭尽全力。UITableView通常是完全过度的,除非您使用一些高级行编辑功能和/或确实需要支持1000+“行”(在我们的应用程序中,它实际上永远不会超过20行)。

额外的好处是,与UITableView附带的所有委托垃圾相比,代码变得异常简单。它只是viewOnLoad中的一个代码循环,看起来很优雅,易于管理。

以下是一些关于如何做到这一点的提示:

使用Storyboard或nib文件,创建ViewController和关联的根视图。将UIScrollView拖到根视图上。向顶层视图添加顶部、底部、左侧和右侧约束,以便UIScrollView填充整个根视图。在UIScrollView中添加一个UIView,并将其称为“容器”。向UIScrollView(其父视图)添加顶部、底部、左侧和右侧约束。关键技巧:还要添加一个“等宽”约束来链接UIScrollView和UIView。注意:您将收到一个错误“滚动视图的可滚动内容高度不明确”,并且您的容器UIView的高度应该为0像素。当应用程序运行时,这两个错误似乎都不重要。为每个“单元”创建笔尖文件和控制器。使用UIView而不是UITableViewCell。在您的根ViewController中,您实际上将所有“行”添加到容器UIView中,并以编程方式添加约束,将它们的左边缘和右边缘链接到容器视图,将其顶部边缘链接到(第一个项目的)容器视图顶部或上一个单元格。然后将最终单元格链接到容器底部。

对我们来说,每一行都在一个笔尖文件中。所以代码看起来像这样:

class YourRootViewController {

    @IBOutlet var container: UIView! //container mentioned in step 4

    override func viewDidLoad() {
        
        super.viewDidLoad()

        var lastView: UIView?
        for data in yourDataSource {

            var cell = YourCellController(nibName: "YourCellNibName", bundle: nil)
            UITools.addViewToTop(container, child: cell.view, sibling: lastView)
            lastView = cell.view
            //Insert code here to populate your cell
        }

        if(lastView != nil) {
            container.addConstraint(NSLayoutConstraint(
                item: lastView!,
                attribute: NSLayoutAttribute.Bottom,
                relatedBy: NSLayoutRelation.Equal,
                toItem: container,
                attribute: NSLayoutAttribute.Bottom,
                multiplier: 1,
                constant: 0))
        }

        ///Add a refresh control, if you want - it seems to work fine in our app:
        var refreshControl = UIRefreshControl()
        container.addSubview(refreshControl!)
    }
}

下面是UITools.addViewToTop的代码:

class UITools {
    ///Add child to container, full width of the container and directly under sibling (or container if sibling nil):
    class func addViewToTop(container: UIView, child: UIView, sibling: UIView? = nil)
    {
        child.setTranslatesAutoresizingMaskIntoConstraints(false)
        container.addSubview(child)
        
        //Set left and right constraints so fills full horz width:
        
        container.addConstraint(NSLayoutConstraint(
            item: child,
            attribute: NSLayoutAttribute.Leading,
            relatedBy: NSLayoutRelation.Equal,
            toItem: container,
            attribute: NSLayoutAttribute.Left,
            multiplier: 1,
            constant: 0))
        
        container.addConstraint(NSLayoutConstraint(
            item: child,
            attribute: NSLayoutAttribute.Trailing,
            relatedBy: NSLayoutRelation.Equal,
            toItem: container,
            attribute: NSLayoutAttribute.Right,
            multiplier: 1,
            constant: 0))
        
        //Set vertical position from last item (or for first, from the superview):
        container.addConstraint(NSLayoutConstraint(
            item: child,
            attribute: NSLayoutAttribute.Top,
            relatedBy: NSLayoutRelation.Equal,
            toItem: sibling == nil ? container : sibling,
            attribute: sibling == nil ? NSLayoutAttribute.Top : NSLayoutAttribute.Bottom,
            multiplier: 1,
            constant: 0))
    }
}

到目前为止,我用这种方法发现的唯一“错误”是UITableView具有一个很好的特性,即在滚动时在视图顶部“浮动”节标题。除非您添加更多的编程,否则上面的解决方案无法做到这一点,但对于我们的特定情况,这一功能并不是100%必需的,当它消失时,没有人注意到。

如果您希望在单元格之间使用分隔符,只需在自定义“单元格”底部添加一个1像素高的UIView,它看起来像分隔符。

确保启用“反弹”和“垂直反弹”,刷新控件才能工作,这样看起来更像是一个表视图。

TableView在您的内容下显示一些空行和分隔符,如果它没有像这个解决方案那样填满整个屏幕。但就我个人而言,我更希望那些空行不在那里——因为单元格高度可变,所以我总是觉得空行在那里很“麻烦”。

希望其他一些程序员在浪费20多个小时试图在自己的应用程序中使用表视图解决问题之前阅读我的帖子