具有动态高度的 UITableViewCell 内的 UITableView

Posted

技术标签:

【中文标题】具有动态高度的 UITableViewCell 内的 UITableView【英文标题】:UITableView inside UITableViewCell with dynamic height 【发布时间】:2017-09-27 07:18:05 【问题描述】:

我需要将 UITableView 放在具有自动布局的 UITableViewCell 中,因为第二个表有不同的行数,而一行可以有不同的高度。 这是我的视图控制器

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)
    

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

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

现在表格已显示,但尺寸太大 我该怎么做才能在单元格底部显示完整的第二个表格而没有空白空间。

【问题讨论】:

【参考方案1】:

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

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

我的表格的完整代码可以在我的GitHub上找到

【讨论】:

这是我在堆栈上找到的最好的帖子,以及 swift y tableView 标签!!!! 我有 1 个问题。如果我们这样做,表格会同时使用所有单元格还是只加载可见单元格并再次使用它们? 不错的方法。谢谢。 如果启用了动态类型,则内部tableview单元格不可见 这加上@MichaelScaria 的解决方案对我有用。【参考方案2】:

在您的第一个表格视图中,在您将单元格出列后将其添加到您的 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 的大小。这种方法对我有用。

【讨论】:

您还需要使单元格的InnerTableView.estimatedRowHeight 小于其最短的单元格。 这个解决方案对我有用。你节省了我的时间。 这加上@Michal 的上述内容在离合器中为我带来了 - 伟大的工作【参考方案3】:

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

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 ))


【讨论】:

【参考方案4】:

您所需要的只是使 ParentTableView 的 cellForRowAt indexPath

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

【讨论】:

以上是关于具有动态高度的 UITableViewCell 内的 UITableView的主要内容,如果未能解决你的问题,请参考以下文章

动态 UITableViewCell 高度内的动态 UITextView?

Swift / 如何使用具有动态高度的 UITextView INSIDE UITableViewCell 与动态高度

具有动态高度的 UITableViewCell 内的 UITableView

具有动态内容、动态布局和动态高度的 UITableViewCell

具有动态高度的 UITableViewCell 中的多个 UILabel

具有动态高度的 UITableViewCell 中的 UILabel