【问题标题】:How to dynamically set tableView height?如何动态设置tableView高度?
【发布时间】:2021-09-18 23:04:00
【问题描述】:

我很难根据内容设置我的tableView 高度。我的单元格具有动态高度,如果我为表格视图硬编码高度,我可以看到它们很好。现在我的问题是:

  • 我的表格视图没有根据我的单元格调整到正确的高度

  • 如果我没有为表格视图设置静态高度,则不会渲染单元格

  • 奖励问题:我使用 SnapKit 来制作约束,当我根据内容大小重新制作约束以适应我的表格视图高度时,它会触发 viewDidLoad 的次数与我拥有的单元格一样多*

这里所说的都是一些代码:

class TableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    private var cellHeight: CGFloat? = UITableView.automaticDimension
    private var hasLoader: Bool = true
    lazy var tableView: UITableView! = UITableView()
    lazy var loadMoreButton: UIButton! = PrimaryButton(textKey: "actionSeeMore")
    lazy var emptyView: UILabel! = UILabel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(Cell.self, forCellReuseIdentifier: "Cell")
        tableView.dataSource = self
        tableView.delegate = self
        tableView.separatorStyle = .none
        tableView.rowHeight = UITableView.automaticDimension
        initActions()
        initLayout()
    }
    
    override func viewWillAppear(_ animated: Bool) {
        loadList(appendItems: true)
    }

    func initLayout() {
        self.view.addSubview(tableView)
        self.view.addSubview(emptyView)
        self.view.addSubview(loadMoreButton)
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.snp.makeConstraints { (make) -> Void in
            make.left.right.leading.trailing.top.equalToSuperview()
            if(self.hasLoader == true) {
                make.bottom.equalTo(self.loadMoreButton.snp.top).offset(-15)
            }
            print("PRINT TABLE HEIGHT", self.tableView.contentSize.height) // It's 0.0
            make.height.equalTo(1000) // Just trying things
        }
        tableView.isScrollEnabled = false
        
        emptyView.translatesAutoresizingMaskIntoConstraints = false
        emptyView.isHidden = true
        emptyView.textAlignment = .center
        emptyView.snp.makeConstraints { (make) -> Void in
            make.left.right.leading.trailing.equalToSuperview()
            make.top.equalToSuperview().offset(8)
        }
        
        self.loadMoreButton.isHidden = true
        if(self.hasLoader == true) {
            self.loadMoreButton.isHidden = false
            loadMoreButton.translatesAutoresizingMaskIntoConstraints = false
            loadMoreButton.snp.makeConstraints { (make) -> Void in
                make.centerX.equalToSuperview()
                make.top.equalTo(tableView.snp.bottom)
            }
        }
    }
    
    func initActions() {
        loadMoreButton.addTarget(self, action: #selector(self.loadMoreOnClick(sender:)), for: .touchUpInside)
    }


    func loadList(appendItems: Bool) {
        DispatchQueue.main.async {
            self.apiClient.getList(completion: { _ in
                DispatchQueue.main.async {
                    self.tableView.reloadData()
                    // Here I normally do tableView.snp.remakeConstraints { (make) in make.height.equalTo(self.tableView.contentSize.height) } which fires viewDidLoad many times (as much as I have cells)
                }
            })
        }
    }

    func loadMore() {
        self.page += 1
        self.loadList(appendItems: true)
    }

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

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! Cell
        let currentResource = resourceList[indexPath.row]
        cell.setResource(resource: currentResource)
        return cell
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        self.tableView.deselectRow(at: indexPath, animated: true)
        let resource = resourceList[indexPath.row]
        if (resource.getRouteParam() != "") {
            router.setRoute(routeName: resource.getRouteName(), routeParam: 
            resource.getRouteParam())
        }
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return UITableView.automaticDimension
    }

为了便于阅读,我省略了部分代码。

【问题讨论】:

  • UITableView 旨在重用单元格并允许它们滚动。如果您的“行”太少以至于您不会滚动,那么您可能(可能)最好使用表格视图。
  • “这么少的行”是什么意思?我有很多行要显示
  • 如果你有“很多行要显示”你为什么要改变表格视图的高度?
  • “我在滚动视图中使用 tableView” ...是的,这是个问题。您的方法将使表格视图(如果您有“很多行”)高得离谱,并且您失去了可重用单元格的内存管理。您可能应该重新考虑您的布局设计。
  • 猜你是对的,我会重新考虑我的布局

标签: ios swift uitableview


【解决方案1】:

尝试以下:

  1. 像这样在UIViewController 子类中声明一个变量。
private var contentSizeObservation: NSKeyValueObservation?
  1. viewDidLoad() 内部或无论何时设置myTableView,开始观察 contentSize 的变化。
contentSizeObservation = myTableView.observe(\.contentSize, options: .new, changeHandler: { [weak self] (tv, _) in
    guard let self = self else { return }
    self.myTableViewHeightConstraint.constant = tv.contentSize.height
})
  1. 不要忘记在deinit 电话中清理它 -
deinit {
    contentSizeObservation?.invalidate()
}

这种方法将确保您的 myTableView 的高度始终与其内容一样大。

【讨论】:

  • 什么是“myTableViewHeightConstraint”?它是变量还是内置变量?
  • 这是您为管理表格视图高度而添加的高度约束,这不是内置的。
  • 您的方法似乎运行良好,但它触发 viewDidLoad 的次数与我再次拥有单元格的次数一样多...这是正常行为吗?
  • + 如果我没有为表格视图设置初始高度值,它不会渲染单元格,因此不会更新 contentSize
  • viewDidLoad 应该只触发一次 - 最初是在应用程序中显示UIViewController.view 时。您应该为您的 tableView 提供一个最小的高度正值,例如 20/40,以便它有机会调用它的数据源方法实现,并基于它知道它的内容有多大。如果最初它的高度为 0,那么它不会调用您的数据源实现,因为它不需要显示任何内容。您必须确保调用您的数据源实现,以便 tableView 知道它需要多大的 contentSize 才能适应所有内容。
猜你喜欢
  • 2016-08-05
  • 1970-01-01
  • 2019-05-23
  • 1970-01-01
  • 2021-12-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-19
相关资源
最近更新 更多