具有动态高度的UITableViewCell内的UITableView

时间:2017-09-27 07:18:05

标签: ios swift uitableview autolayout uikit

我需要将UITableView放在UITableViewCell中,并使用自动布局,因为第二个表有不同的行数,一行可以有不同的高度。
这是我的ViewController

class ViewController: UIViewController {

    let tableView = UITableView()
    let cellId = "firstTableCellId"

    override func viewDidLoad() {
        super.viewDidLoad()
        setupView()
        tableView.reloadData()
        view.backgroundColor = UIColor.gray
    }

    func setupView() {
        tableView.delegate = self
        tableView.dataSource = self
        tableView.separatorStyle = .none
        tableView.register(NextTable.self, forCellReuseIdentifier: cellId)
        tableView.backgroundColor = UIColor.green
        tableView.separatorStyle = .singleLine

        view.addSubview(tableView)
        view.addConstraintsWithFormat("V:|-60-[v0]-5-|", views: tableView)
        view.addConstraintsWithFormat("H:|-8-[v0]-8-|", views: tableView)
    }
}

extension ViewController: UITableViewDelegate {

}

extension ViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 2
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return UITableViewAutomaticDimension
    }

    func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
        return 80
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! NextTable
        cell.layoutIfNeeded()
        return cell
    }
}

和NextTable是第一个表格中的单元格

class NextTable: UITableViewCell {

    var myTableView: UITableView!
    let cellId = "nextTableCellId"

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        backgroundColor = UIColor.brown
        setupView()
        myTableView.reloadData()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    let label: UILabel = {
        let label = UILabel()
        label.numberOfLines =  0
        label.lineBreakMode = .byWordWrapping
        label.text = "Next table:"
        label.textColor = UIColor.black
        label.sizeToFit()
        label.backgroundColor = UIColor.cyan
        return label
    }()

    func setupView() {
        myTableView = UITableView()
        myTableView.delegate = self
        myTableView.dataSource = self
        myTableView.separatorStyle = .singleLineEtched
        myTableView.backgroundColor = UIColor.blue
        myTableView.register(TableCell.self, forCellReuseIdentifier: cellId)
        myTableView.isScrollEnabled = false

        addSubview(myTableView)
        addSubview(label)
        addConstraintsWithFormat("H:|-30-[v0]-30-|", views: myTableView)
        addConstraintsWithFormat("H:|-30-[v0]-30-|", views: label)

        addConstraint(NSLayoutConstraint(item: label, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 15))
        addConstraint(NSLayoutConstraint(item: myTableView, attribute: .top, relatedBy: .equal, toItem: label, attribute: .bottom, multiplier: 1.0, constant: 0))
        addConstraint(NSLayoutConstraint(item: label, attribute: .bottom, relatedBy: .equal, toItem: myTableView, attribute: .top, multiplier: 1.0, constant: 0))
        addConstraint(NSLayoutConstraint(item: myTableView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: -15))
    }
}

extension NextTable: UITableViewDelegate {

}

extension NextTable: UITableViewDataSource {

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

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 5
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return UITableViewAutomaticDimension
    }

    func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
        return 60
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! TableCell
        cell.layoutIfNeeded()
        return cell
    }
}

第二张表中的单元格

class TableCell: UITableViewCell {
    let label: UILabel = {
        let label = UILabel()
        label.numberOfLines =  0
        label.lineBreakMode = .byWordWrapping
        label.text = "Some text"
        label.textColor = UIColor.black
        label.sizeToFit()
        label.backgroundColor = UIColor.red
        return label
    }()

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        backgroundColor = UIColor.yellow
        setupView()
    }

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

    func setupView(){
        addSubview(label)
        addConstraintsWithFormat("V:|-8-[v0]-8-|", views: label)
        addConstraintsWithFormat("H:|-5-[v0]-5-|", views: label)
    }
}

这是我的代码的效果 Program result
没有第二个表,所以我创建了新类并在单元格中将其用作新表视图

class InnerTableView: UITableView {
    override var intrinsicContentSize: CGSize {
        return self.contentSize
    }
}

现在桌子显示但是尺寸太大了 Result of InnerTableView
如何在单元格底部显示没有空白空间的完整第二个表格。

4 个答案:

答案 0 :(得分:3)

在您的第一个表视图中,在您的单元格出列后将其添加到cellForRowAt(可能需要稍微调整以适合您的实现):

cell.tableView.reloadData()
DispatchQueue.main.async {
    cell.tableView.scrollToRow(at: IndexPath(row: cell.numberOfRowsInInnerTableView.count.count - 1, section: 0), at: .bottom, animated: false)
}
DispatchQueue.main.async {
    cell.tableView.invalidateIntrinsicContentSize()
    cell.tableView.layoutIfNeeded()
    self.updateHeight()
}

然后在同一个类(外部表视图)中定义一个函数updateHeight

func updateHeight() {
    UIView.setAnimationsEnabled(false)
    tableView.beginUpdates()
    tableView.endUpdates()
    UIView.setAnimationsEnabled(true)
}

显然,这有点hacky,但基本上它允许单元格的InnerTableView知道所有内部单元格的实际高度,并适当地调整外部tableview的大小。这种方法对我有用。

答案 1 :(得分:0)

我在表格视图中添加了属性覆盖:

override var intrinsicContentSize: CGSize {
    self.layoutIfNeeded()
    return self.contentSize
}

我可以在GitHub

找到我桌上的完整代码

答案 2 :(得分:0)

您所需要做的就是使ParentTableView的 cellForRowAt indexPath

中的内容大小无效
parentCell.InsideTableview.invalidateIntrinsicContentSize()

答案 3 :(得分:0)

我尝试了上述解决方案,但对我没有用。我做了一些小改动,然后对我来说效果很好。

A)在我的ViewController中:

//注意:TestQuestionAnsOptionCell是外部单元格

if let cell = tableView.dequeueReusableCell(withIdentifier: String(describing:TestQuestionAnsOptionCell.self), for: indexPath) as? TestQuestionAnsOptionCell {
            cell.reloadData()
            setUpInnerCellListner(cell: cell)
            cell.selectionStyle = .none
            return cell
  }


private func setUpInnerCellListner(cell:TestQuestionAnsOptionCell) {
    cell.reloadTable = { // Closure called from TestQuestionAnsOptionCell
        DispatchQueue.main.async {
            UIView.setAnimationsEnabled(false)
            self.tbQuestion.beginUpdates()
            self.tbQuestion.endUpdates()
            UIView.setAnimationsEnabled(true)
        }
    }
}

此处使用的扩展名:

extension NSObject {
class var className: String {
    return String(describing: self)
}
}

B)在TestQuestionAnsOptionCell中:

class TestQuestionAnsOptionCell: UITableViewCell {

var tblOptions: OwnTableView = OwnTableView()
var reloadTable:(()->(Void))?

override func awakeFromNib() {
    super.awakeFromNib()
    setupView()
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}

private func setupView() {
    tblOptions.estimatedRowHeight = 60.0
    tblOptions.rowHeight = UITableView.automaticDimension
    tblOptions.delegate = self
    tblOptions.dataSource = self
    tblOptions.separatorStyle = .none
    tblOptions.register(UINib(nibName: TestQuestionOptionCell.className, bundle: nil), forCellReuseIdentifier: TestQuestionOptionCell.className)
    tblOptions.isScrollEnabled = false
    
    addSubview(tblOptions)
    addConstraintsWithFormat("H:|-30-[v0]-30-|", views: tblOptions)
    
    addConstraint(NSLayoutConstraint(item: tblOptions, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 15))
    addConstraint(NSLayoutConstraint(item: tblOptions, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: -15))
}

func reloadData()  {
    tblOptions.reloadData()
}

}

借助TableView委托和数据源,我使用了以下内容:

extension TestQuestionAnsOptionCell:UITableViewDelegate, UITableViewDataSource {

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    //NOTE: TestQuestionOptionCell is the inner cell

    if let cell = tableView.dequeueReusableCell(withIdentifier: TestQuestionOptionCell.className, for: indexPath) as? TestQuestionOptionCell {
        cell.updateCell()
        cell.selectionStyle = .none
        return cell
    }
    return UITableViewCell()
}

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return UITableView.automaticDimension
}
 
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    tblOptions.invalidateIntrinsicContentSize()
    tblOptions.layoutIfNeeded()
    reloadTable?()//Closure to update outer tableview
}

}

C)内部表视图用作:

class OwnTableView: UITableView {
override var intrinsicContentSize: CGSize {
    self.layoutIfNeeded()
    return self.contentSize
}

override var contentSize: CGSize {
    didSet{
        self.invalidateIntrinsicContentSize()
    }
}
}

D)UIView的扩展名为:

extension UIView {

func addConstraintsWithFormat(_ format: String, views: UIView...) {
    var viewsDictionary = [String:UIView]()
    for(index, view) in views.enumerated() {
        let key = "v\(index)"
        view.translatesAutoresizingMaskIntoConstraints = false
        viewsDictionary[key] = view
    }
    addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutConstraint.FormatOptions(), metrics: nil, views: viewsDictionary ))
}
}