如果禁用滚动,您能否根据显示的行数更新 UITableView 的固有内容大小?

Can you get a UITableView's intrinsic content size to update based on the number of rows shown if scrolling is disabled?

我们有一部分 UI 这是一小部分标签,旁边有色样。我接手的设计中有六个硬编码在布局中,即使实际数据是动态的,这意味着如果我们只需要显示三个,我们必须明确隐藏三个,这也会破坏页面的平衡.更糟糕的是,每个 'items' 实际上都由几个子视图组成,因此具有六个硬编码项目的屏幕有十八个 IBOutlets。

我想做的是使用 UITableView 来表示屏幕的这一小部分,因为它不会滚动,我想知道您是否可以使用 AutoLayout 来配置UITableView 的内在内容高度基于行数。

目前我有一个测试页面,其中 UITableView 垂直限制在中心,但没有高度限制,因为我希望 table 的固有内容大小反映可见行.我还禁用了 table 上的滚动。当我重新加载 table 时,我调用 updateConstraints。但是 table 仍然没有调整大小。

注意:我们不能使用 UIStackView(这对于此来说是完美的),因为我们必须以 iOS8 为目标,而直到 iOS9 才引入,因此这个解决方案。

有没有人能做类似我们需求的事情?

这是可以做到的,请看下面的一个非常简单的(粗略的 - 旋转不能正常工作!)示例,它允许您通过在中输入一个数字来更新 table 视图的大小文本字段并使用按钮重置。

import UIKit

class ViewController: UIViewController {

    var tableViewController : FlexibleTableViewController!
    var textView : UITextView!
    var button : UIButton!
    var count : Int! {
        didSet {
            self.refreshDataSource()
        }
    }
    var dataSource : [Int]!
    let rowHeight : CGFloat = 50

    override func viewDidLoad() {
        super.viewDidLoad()

        // Configure

        self.tableViewController = FlexibleTableViewController(style: UITableViewStyle.plain)

        self.count = 10
        self.tableViewController.tableView.backgroundColor = UIColor.red

        self.textView = UITextView()
        self.textView.textAlignment = NSTextAlignment.center
        self.textView.textColor = UIColor.white
        self.textView.backgroundColor = UIColor.blue

        self.button = UIButton()
        self.button.setTitle("Reset", for: UIControlState.normal)
        self.button.setTitleColor(UIColor.white, for: UIControlState.normal)
        self.button.backgroundColor = UIColor.red
        self.button.addTarget(self, action: #selector(self.updateTable), for: UIControlEvents.touchUpInside)

        self.layoutFrames()

        // Assemble
        self.view.addSubview(self.tableViewController.tableView)
        self.view.addSubview(self.textView)
        self.view.addSubview(self.button)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    func refreshDataSource() -> Void {
        if let _ = self.dataSource {
            if !self.dataSource.isEmpty {
                self.dataSource.removeAll()
            }
        }
        else
        {
            self.dataSource = [Int]()
        }

        for count in 0..<self.count {
            self.dataSource.append(count)
        }

        self.tableViewController.dataSource = self.dataSource
        self.tableViewController.tableView.reloadData()
        if let _ = self.view {
            self.layoutFrames()
            self.view.setNeedsDisplay()
        }
    }

    func updateTable() -> Void {
        guard let _ = self.textView.text else { return }
        guard let validNumber = Int(self.textView.text!) else { return }

        self.count = validNumber
    }

    func layoutFrames() -> Void {

        if self.tableViewController.tableView != nil {
            self.tableViewController.tableView.frame = CGRect(origin: CGPoint(x: self.view.frame.width / 2 - 100, y: 100), size: CGSize(width: 200, height: CGFloat(self.dataSource.count) * self.rowHeight))
            NSLog("\(self.tableViewController.tableView.frame)")
        }

        if self.textView != nil {
            self.textView.frame = CGRect(origin: CGPoint(x: 50, y: 100), size: CGSize(width: 100, height: 100))
        }

        if self.button != nil {
            self.button.frame = CGRect(origin: CGPoint(x: 50, y: 150), size: CGSize(width: 100, height: 100))
        }
    }
}

class FlexibleTableViewController : UITableViewController {

    var dataSource : [Int]!

    override init(style: UITableViewStyle) {
        super.init(style: style)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.dataSource.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")

        let cell = tableView.dequeueReusableCell(withIdentifier: "cell") ?? UITableViewCell()

        cell.frame = CGRect(origin: CGPoint(x: 10, y: 5), size: CGSize(width: 180, height : 40))
        cell.backgroundColor = UIColor.green

        return cell
    }

}

正如已经指出的那样,这是否是一个好主意,是另一个问题!希望对您有所帮助!

好吧,与 UITextView 不同,它看起来 UITableView 从来没有 returns 基于可见行的固有大小。但是通过子类实现这并不是什么大不了的事情,特别是如果只有一个部分,没有页眉或页脚,并且行的高度是固定的。

class AutoSizingUiTableView : UITableView
{
    override func intrinsicContentSize() -> CGSize
    {
        let requiredHeight = rowCount * rowHeight
        return CGSize(width: UIView.noIntrinsicMetric, height: CGFloat(requiredHeight))
    }
}

我会留给 reader 来弄清楚如何获得他们自己的 rowCount。如果您有可变高度、多个部分等,则相同。您只需要更多逻辑。

通过这样做,它与 AutoLayout 配合得很好。我只希望它能自动处理。

// 定义这只小狗:

class AutoTableView: UITableView {   
 
    override func layoutSubviews() {
        super.layoutSubviews()
        self.invalidateIntrinsicContentSize()
    }
    
    override var intrinsicContentSize: CGSize {
        get {
            var height:CGFloat = 0;
            for s in 0..<self.numberOfSections {
                let nRowsSection = self.numberOfRows(inSection: s)
                for r in 0..<nRowsSection {
                    height += self.rectForRow(at: IndexPath(row: r, section: s)).size.height;
                }
            }
            return CGSize(width: UIView.noIntrinsicMetric, height: height)
        }
        set {
        }
    }
}

并使其成为您在 IB 中的 class。 obs:这是如果你的 class 只是细胞和狗屎。如果它有页眉、页脚或其他东西,不知道。它不会工作。对我来说它有效

和平

来自 no_ripcord 的版本考虑了页眉和页脚高度

final // until proven otherwise
class IntrinsicallySizedTableView: UITableView {

  override func layoutSubviews() {
    super.layoutSubviews()
    self.invalidateIntrinsicContentSize()
  }
  
  override var intrinsicContentSize: CGSize {
    guard let dataSource = self.dataSource else {
      return super.intrinsicContentSize
    }
    var height: CGFloat = (tableHeaderView?.intrinsicContentSize.height ?? 0)
      + contentInset.top + contentInset.bottom
    if let footer = tableFooterView {
      height += footer.intrinsicContentSize.height
    }
    let nsections = dataSource.numberOfSections?(in: self) ?? self.numberOfSections
    for section in 0..<nsections {
      let sectionheader = rectForHeader(inSection: section)
      height += sectionheader.height
      let sectionfooter = rectForFooter(inSection: section)
      height += sectionfooter.height
      let nRowsSection = self.numberOfRows(inSection: section)
      for row in 0..<nRowsSection {
        height += self.rectForRow(at: IndexPath(row: row, section: section)).size.height
      }
    }
    return CGSize(width: UIView.noIntrinsicMetric, height: height)
  }
}