如何解决深层 tableView 中的自动高度问题?

Posted

技术标签:

【中文标题】如何解决深层 tableView 中的自动高度问题?【英文标题】:how to fix auto height problem in deep level tableView? 【发布时间】:2019-09-10 08:49:06 【问题描述】:

我正在创建深层 tableView(主 tableView 有两个单元格,它们也有 tableViews,它们也有其他 tableViews)

tableViews 和单元格的数量是有限的(这意味着我不需要递归)

对于 rowHeights,我使用的是 UITableView.automaticDimension,但它不能正常工作。

这是故事板的截图:

https://imgur.com/a/2tEwwZ9

这是一个结果:

https://imgur.com/a/XLFiLKh

类 TipsCountriesTableViewCell: UITableViewCell

@IBOutlet weak var tipsCountriesTableView: TipsCountriesTableView!
@IBOutlet weak var tipsCountriesHeightConstraint: NSLayoutConstraint!

override func awakeFromNib() 
    tipsCountriesTableView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)


override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) 
    if let obj = object as? UITableView 
        if obj == tipsCountriesTableView && keyPath == "contentSize" 
            if let newSize = change?[NSKeyValueChangeKey.newKey] as? CGSize 
                tipsCountriesHeightConstraint.constant = tipsCountriesTableView.contentSize.height
            
        
    


deinit 
    self.tipsCountriesTableView.removeObserver(self, forKeyPath: "contentSize")

类 TipsCitiesTableViewCell: UITableViewCell

@IBOutlet weak var tipsCitiesTableView: TipsCitiesTableView!
@IBOutlet weak var tipsCitiesHeightConstraint: NSLayoutConstraint!

override func awakeFromNib() 
    tipsCitiesTableView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)


override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) 
    if let obj = object as? UITableView 
        if obj == tipsCitiesTableView && keyPath == "contentSize" 
            if let newSize = change?[NSKeyValueChangeKey.newKey] as? CGSize 
                tipsCitiesHeightConstraint.constant = tipsCitiesTableView.contentSize.height
            
        
    


deinit 
    self.tipsCitiesTableView.removeObserver(self, forKeyPath: "contentSize")

类 TipsTableViewCell: UITableViewCell

@IBOutlet weak var tipsTableView: TipsTableView!
@IBOutlet weak var tipsTableViewHeightConstraint: NSLayoutConstraint!

override func awakeFromNib() 
    tipsTableView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)


override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) 
    if let obj = object as? UITableView 
        if obj == tipsTableView && keyPath == "contentSize" 
            if let newSize = change?[NSKeyValueChangeKey.newKey] as? CGSize 
                tipsTableViewHeightConstraint.constant = tipsTableView.contentSize.height
            
        
    


deinit 
    self.tipsTableView.removeObserver(self, forKeyPath: "contentSize")

class TipsContinentsTableView: UITableView, UITableViewDataSource, UITableViewDelegate 

    var data: [ItineraryTipsContinent]?

    required init?(coder aDecoder: NSCoder) 
        super.init(coder: aDecoder)
        dataSource = self
        delegate = self
    

    func numberOfSections(in tableView: UITableView) -> Int 
        return data?.count ?? 0
    

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        return 2
    

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
        guard indexPath.row == 0 else 
            let cell = tableView.dequeueReusableCell(withIdentifier: "TipsCountriesTableViewCell") as! TipsCountriesTableViewCell
            cell.tipsCountriesTableView.data = data?[indexPath.section].countries
            return cell
        
        let cell = tableView.dequeueReusableCell(withIdentifier: "TipsTableViewCell") as! TipsTableViewCell
        cell.tipsTableView.data = data?[indexPath.section].tips
        return cell
    

    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? 
        let sectionHeaderView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "TravelInfoTableViewSectionHeaderView") as! TravelInfoTableViewSectionHeaderView
        guard let data = data else 
            return sectionHeaderView
        
        sectionHeaderView.cityLabel.text = data[section].name
        sectionHeaderView.arrowImageView.isHidden = false
        return sectionHeaderView
    


class TipsCountriesTableView: UITableView, UITableViewDataSource, UITableViewDelegate 

    private let reuseIdentifier = "TravelInfoTableViewSectionHeaderView"

    var data: [ItineraryTipsCountry]?

    required init?(coder aDecoder: NSCoder) 
        super.init(coder: aDecoder)
        let nib = UINib(nibName: reuseIdentifier, bundle: nil)
        register(nib, forHeaderFooterViewReuseIdentifier: reuseIdentifier)
        sectionHeaderHeight = 50.0
        rowHeight = UITableView.automaticDimension
        dataSource = self
        delegate = self
    

    func numberOfSections(in tableView: UITableView) -> Int 
        return data?.count ?? 0
    

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        return 2
    

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
        guard indexPath.row == 0 else 
            let cell = tableView.dequeueReusableCell(withIdentifier: "TipsCitiesTableViewCell") as! TipsCitiesTableViewCell
            cell.tipsCitiesTableView.data = data?[indexPath.section].cities
            return cell
        
        let cell = tableView.dequeueReusableCell(withIdentifier: "TipsTableViewCell") as! TipsTableViewCell
        cell.tipsTableView.data = data?[indexPath.section].tips
        return cell
    

    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? 
        let sectionHeaderView = tableView.dequeueReusableHeaderFooterView(withIdentifier: reuseIdentifier) as! TravelInfoTableViewSectionHeaderView
        guard let data = data else 
            return sectionHeaderView
        
        sectionHeaderView.cityLabel.text = data[section].name
        sectionHeaderView.arrowImageView.isHidden = false
        return sectionHeaderView
    


class TipsCitiesTableView: UITableView, UITableViewDataSource, UITableViewDelegate 

    private let reuseIdentifier = "TravelInfoTableViewSectionHeaderView"

    var data: [ItineraryTipsCity]?

    required init?(coder aDecoder: NSCoder) 
        super.init(coder: aDecoder)
        let nib = UINib(nibName: reuseIdentifier, bundle: nil)
        register(nib, forHeaderFooterViewReuseIdentifier: reuseIdentifier)
        sectionHeaderHeight = 50.0
        rowHeight = UITableView.automaticDimension
        dataSource = self
        delegate = self
    

    func numberOfSections(in tableView: UITableView) -> Int 
        return data?.count ?? 0
    

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        return 1
    

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
        let cell = tableView.dequeueReusableCell(withIdentifier: "TipsTableViewCell") as! TipsTableViewCell
        cell.tipsTableView.data = data?[indexPath.section].tips
        return cell
    

    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? 
        let sectionHeaderView = tableView.dequeueReusableHeaderFooterView(withIdentifier: reuseIdentifier) as! TravelInfoTableViewSectionHeaderView
        guard let data = data else 
            return sectionHeaderView
        
        sectionHeaderView.cityLabel.text = data[section].name
        sectionHeaderView.arrowImageView.isHidden = false
        return sectionHeaderView
    


class TipsTableView: UITableView, UITableViewDataSource, UITableViewDelegate 

    private let reuseIdentifier = "TravelInfoTableViewSectionHeaderView"

    var data: [ItineraryTip]?

    required init?(coder aDecoder: NSCoder) 
        super.init(coder: aDecoder)
        let nib = UINib(nibName: reuseIdentifier, bundle: nil)
        register(nib, forHeaderFooterViewReuseIdentifier: reuseIdentifier)
        rowHeight = UITableView.automaticDimension
        dataSource = self
        delegate = self
    

    func numberOfSections(in tableView: UITableView) -> Int 
        return data?.count ?? 0
    

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        return 1
    

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
        let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
        cell.textLabel?.text = data?[indexPath.row].text
        cell.textLabel?.font = UIFont.poppinsFont(ofSize: 12.0)
        cell.textLabel?.numberOfLines = 0
        return cell
    

    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? 
        let sectionHeaderView = tableView.dequeueReusableHeaderFooterView(withIdentifier: reuseIdentifier) as! TravelInfoTableViewSectionHeaderView
        guard let data = data else 
            return sectionHeaderView
        
        sectionHeaderView.cityLabel.alpha = 0.48
        sectionHeaderView.cityLabel.font = UIFont.poppinsFont(ofSize: 13.0)
        sectionHeaderView.cityLabel.text = data[section].title
        sectionHeaderView.arrowImageView.isHidden = false
        return sectionHeaderView
    

【问题讨论】:

Self sizing tableview inside self sizing tableview cell的可能重复 【参考方案1】:

您必须将恒定高度设置为内部UITableView。为 tableView 的“contentSize”添加观察者。当内容大小发生变化时,您会收到通知。所以你可以在这里设置tipsCountriesHeightConstraint

TipsCountriesTableViewCell 类的awakeFromNib 中添加观察者,如下所示,

tipsCountriesTableView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)

观察值如下,

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) 
    if let obj = object as? UITableView 
        if obj == self.tipsCountriesTableView && keyPath == "contentSize" 
            if let newSize = change?[NSKeyValueChangeKey.newKey] as? CGSize 
                tipsCountriesHeightConstraint.constant = tipsCountriesTableView.contentSize.height
            
        
    

删除deinit中的观察者

deinit 
    self.tipsCountriesTableView.removeObserver(self, forKeyPath: "contentSize")

TipsCountriesTableViewCell 项目必须具有顶部、底部约束。

【讨论】:

感谢@Komal Goyani 的快速响应。我已经向所有单元格添加了观察者,它看起来更好,但仍然缺少一些单元格.. 这是结果 imgur.com/a/GwFmuAU 意思是,不能向下滚动? @BekaGelashvili @Komai Goyani 是的 如果将主 tableView 设置为恒定高度,则需要将该主 tableView 放在滚动视图中。 @BekaGelashvili @komai Goyani 只有主表是可滚动的【参考方案2】:

您需要通过类似这样的方式计算*** UItableView 单元格高度的大小,

            var size = CGSize()
            let cell = YourUITableViewCell()
            cell.textLabel?.text = data?[indexPath.row].text
            let fitting = CGSize(width: cell.frame.size.width, height: 1)
            size = cell.contentView.systemLayoutSizeFitting(fitting,
                                                            withHorizontalFittingPriority: .required,
                                                            verticalFittingPriority: UILayoutPriority(1))
            return size.height

如果 UILabel 是自定义的,则需要设置 UILabel 的底部约束以低于 1000 的优先级查看。

【讨论】:

以上是关于如何解决深层 tableView 中的自动高度问题?的主要内容,如果未能解决你的问题,请参考以下文章

TextView的自动高度,在tableView的单元格中使用autolayout

使用 swift 在不使用自动尺寸的情况下获取 tableView 中动态文本视图的高度

tableView 未设置自动行高

如何使用 Masonry 自动布局 UItableviewCell 高度

TableView 中的自动布局问题

表格视图的自动高度