使用自动布局将UITableView的高度设置为其内容的高度

44

我有一个视图,里面有两个标签和一个表格视图。我想要标签1始终在表格视图的上方,而标签2在表格视图下方。问题是表格视图需要自适应大小,也就是说需要增加或减少高度。

现在我有一个约束,它规定表格视图的高度始终等于85,并且有一个@IBOutlet连接到高度约束,在那里我可以更改常量。

我猜想我需要将常量更改为所有单元格的高度,但我不确定如何操作。

菜单约束


1
另一种解决方案是创建一个UITableView的子类,将其自己的intrinsicContentSize.height设置为其contentSize.height。请参见https://dev59.com/uGQm5IYBdhLWcg3wyhfk#17335818。 - rob mayoff
在我看来,@robmayoff的答案更具动态性,比joern的答案更好。 - Altimac
我知道这不是你问题的答案,但你考虑过使用UIStackView而不是TableView吗? - Emre Önder
8个回答

66

您需要在UIViewController中覆盖updateViewConstraints()方法,并将高度约束的常量设置为tableView.contentSize.height:

override func updateViewConstraints() {
    tableHeightConstraint.constant = tableView.contentSize.height
    super.updateViewConstraints()
}

那么您必须确保Label2具有与表视图底部的上约束greaterThanOrEqual,并且还必须将表视图的高度约束优先级从Required更改为High,以避免当表视图的contentHeight大于可用高度时出现冲突的约束。


7
在viewDidLayoutSubviews方法中添加这段代码对我有效。 - Hanushka Suren
2
据我所知,viewDidLayoutSubviews在布局系统完成计算框架后被调用,因此如果您在此更改约束,则会再次触发布局过程,最终应该会创建无限递归。 - vahotm
@vahotm:是啊,但是updateViewConstraints对我没用。不知道为什么:( - Hanushka Suren
1
虽然我认为这是一个好的解决方案,但它需要你根据约束来调整tableview的高度。我发现下面nova提供的解决方案更好,因为它不依赖于使用约束来改变高度。在我的情况下,我的tableView是一个子视图,父视图需要根据tableView的大小调整自己的框架。 - Nate4436271
1
@GMHSJ,在updateViewConstraints方法的末尾调用super.updateViewConstraints()方法是否有帮助?因为根据文档,这是覆盖该方法的正确方式。 - Iulian Onofrei
我使用了相同的方法,但现在我得到了一个比其内容更长的表格。 - keshav

62

还有另一种方法可以做到这一点。 您可以在表视图contentSize变量上添加一个观察者,并在表视图内容发生更改时自我调整大小。

@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var tableHeightConstraint: NSLayoutConstraint!

override func viewDidLoad() {
    super.viewDidLoad()
    self.tableView.addObserver(self, forKeyPath: "contentSize", options: NSKeyValueObservingOptions.new, context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    tableView.layer.removeAllAnimations()
    tableHeightConstraint.constant = tableView.contentSize.height
    UIView.animate(withDuration: 0.5) {
        self.updateViewConstraints()
    }

}

它对我有效。但是'observeValueForKeyPath'方法被调用了很多次。 - Kirti Nikam
当你使用NSKeyValueObservingOptions.new观察一个键时,每当该键更新到新值时,都会调用@iKT,因此无论何时,您都可以删除该观察者。 - Hamed Nova
4
有用的。不是一种优雅的方法。实际上,多年来苹果也没有像“tableview.contentSize”这样使用“didFinishLoad”事件来实现更时尚的方式。但目前这是最清晰的方法,可以完成此常规任务而无需创建特定的类或扩展。 - Bruno Reis Portela
记得在 deinit 中使用 removeObserver 来移除观察者。 - lawicko
如何在deinit中移除观察者 @HamedNova - Poornima Mishra

4
以下代码对我有效。
  1. 为 tableViewHeight 创建一个 outlet。
@IBOutlet weak var tableViewHeight: NSLayoutConstraint!
var tableViewHeight: CGFloat = 0  // Dynamically calcualted height of TableView.

对于单元格的动态高度,我使用了以下代码:
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    return tableView.estimatedRowHeight
}

对于TableView的高度,其中包括TableView单元格的动态高度:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    print(cell.frame.size.height, self.tableViewHeight)
    self.tableViewHeight += cell.frame.size.height
    tableViewBillsHeight.constant = self.tableViewHeight
}

解释:

在创建 TableView 单元格后,我们获取即将显示的单元格的框架高度,并将单元格的高度添加到主 TableView 中。


1
在Swift 4.2和Xcode 10.1中,您有两个选项可以动态基于内容设置UITableView的高度。
1)如果您获取内容大小,请在viewDidLayoutSubviews()中使用此代码。
DispatchQueue.main.async {
  var frame = self.TblView.frame
  frame.size.height = self.TblView.contentSize.height
   self.TblView.frame = frame
}

2) 基于表视图高度约束更新表视图高度。当从服务器获取响应并重新加载表视图时,请编写此代码。

DispatchQueue.main.async {
    self.TblViewHeightConstraint.constant = CGFloat((self.array.count) * 30)//Here 30 is my cell height
     self.TblView.reloadData()
}

0
override func viewDidLayoutSubviews() {
      super.viewDidLayoutSubviews()      
           let contentheight = tableView.contentSize.height
           self.tableHeightConstraint.constant = contentheight
  }

0

将 @nova 的答案扩展到 Swift 4

tableViewSizeObservation = tableView.observe(\.contentSize) { [weak self] (tableView, _) in
   tableView.layer.removeAllAnimations()
   self?.tableViewHeightContraint.constant = tableView.contentSize.height
   UIView.animate(withDuration: 0.5) {
         self?.view.updateConstraints()
         self?.view.layoutIfNeeded()
   }
}

deinit {
    tableViewSizeObservation?.invalidate()
}

0
一种更现代的做法是这样的:
override func updateViewConstraints() {
    tableView.heightAnchor.constraint(equalToConstant: tableView.contentSize.height).isActive = true
    super.updateViewConstraints()
}

但是每次调用updateViewConstraints时,您都会创建一个新的约束。因此,您正在累积多个高度约束,因此如果tableView.contentSize更改,则会有具有冲突值的高度约束。因此,值得存储高度约束并始终更新该约束(可以在viewDidLoad中创建它)。此外,即使大小永远不会更改,自动布局也必须处理额外的约束。 - androidguy

-1

要使TableView的高度根据内容大小自动调整,请在ViewController类中使用以下代码。

//constraintTableViewHeight :- tableview height constraint 

override func viewWillLayoutSubviews() {
    super.updateViewConstraints()
    self.constraintTableViewHeight.constant = self.tableView.contentSize.height
}

并且添加这个

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    self.viewWillLayoutSubviews()
}

这段代码可以根据内容大小自动调整表格视图的高度。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接