【问题标题】:UITableView inside UITableViewCell with dynamic height具有动态高度的 UITableViewCell 内的 UITableView
【发布时间】: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
    }
}

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

【问题讨论】:

    标签: ios swift uitableview autolayout uikit


    【解决方案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()
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-08-16
        • 1970-01-01
        • 2013-04-28
        • 1970-01-01
        • 2020-02-07
        • 2021-05-18
        • 2013-09-02
        • 2012-10-18
        相关资源
        最近更新 更多