Swift 4.2 TableViewCell以编程方式实现动态高度

时间:2019-03-15 08:44:28

标签: ios swift uitableview nslayoutconstraint

这不是重复的问题,因为此问题没有真正的解决方案

我正在尝试使用约束通过其内容实现UITableViewcell动态高度,但收到布局警告:

  

将尝试通过打破约束来恢复   

     

在UIViewAlertForUnsatisfiableConstraints处创建符号断点   在调试器中捕获它。中的方法   UIView中列出的UIConstraintBasedLayoutDebugging类别,在    可能也有帮助。 2019-03-15   12:27:52.085475 + 0400 TableCellDynamicHeight [31984:1295380]   [LayoutConstraints]无法同时满足约束。     以下列表中的约束中至少有一个是   你不要试试这个:(1)查看每个约束并尝试   找出你不期望的东西; (2)找到添加了   不必要的约束或约束并进行修复。 (       “”,       “”,       “”,       “”)

我检查了一些线程: Dynamic tableViewCell height

Dynamic Height Issue for UITableView Cells (Swift)

Swift 3 - Custom TableViewCell dynamic height - programatically

什么是正确的解决方案,我缺少什么?

ViewController:

import UIKit

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    lazy var tableView: UITableView = {
        let table = UITableView()
        table.backgroundColor = .white
        table.translatesAutoresizingMaskIntoConstraints = false
        table.register(TableViewCell.self, forCellReuseIdentifier: "cellId")
        table.dataSource = self
        table.delegate = self
        return table
    }()


    let arr:[Int:UIColor] = [345: UIColor.random, 422: .random, 23: .random, 344: .random,200: .random,140: .random]

    var pickerDataVisitLocation = [203: "Home", 204: "Hospital", 205: "Other"]

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .red

        self.view.addSubview(tableView)
//
        tableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
        tableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
        tableView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
        tableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
        tableView.tableFooterView = UIView()
    }
}

extension ViewController {

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

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

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! TableViewCell
        let value:UIColor = Array(arr)[indexPath.row].value
        let key = Array(arr)[indexPath.row].key

        cell.setupViews(he: CGFloat(key), color: value)
        return cell
    }

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

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

extension UIColor {
    static var random: UIColor {
        return UIColor(red: .random(in: 0...1),
                       green: .random(in: 0...1),
                       blue: .random(in: 0...1),
                       alpha: 1.0)
    }
}

TableViewCell:

    import UIKit

    class TableViewCell: UITableViewCell {


        override func awakeFromNib() {
            super.awakeFromNib()


        }

        override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
            super.init(style: style, reuseIdentifier: reuseIdentifier)


        }

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

        func setupViews(he:CGFloat, color:UIColor) {

            let v:UIView = UIView()
            v.translatesAutoresizingMaskIntoConstraints = false
            self.addSubview(v)

            v.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
            v.backgroundColor = color
            v.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
            v.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
            v.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
            v.heightAnchor.constraint(equalToConstant: he).isActive = true
            #warning("here is constraint error conflict with bottomAnchor and heightAnchor, need correct solution")
        }

    }

3 个答案:

答案 0 :(得分:2)

在您所处的情况下,数据源arr中的高度是可用的,因此您不需要:

  1. 高度限制
  2. estimatedHeightForRowAtIndexPath

您所需要做的就是返回heightForRowAtIndexPath中的实际高度,但是首先您的数据源arr:[Int:UIColor]Dictionary,我将不依赖其顺序,而是将其更改为{{ {} {1}}中的1}}:

Array

现在使用以下UITableView委托/数据源方法:

Tuples

由于您不需要高度限制,因此我从var dataSource: [(height: CGFloat, color: UIColor)] = [ (345, .random), (422, .random), (23, .random), (344, .random), (200, .random), (140, .random) ] 方法中删除了extension ViewController: UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.dataSource.count } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return dataSource[indexPath.row].height } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! TableViewCell cell.setupViews(color: dataSource[indexPath.row].color) return cell } } 参数

答案 1 :(得分:1)

您做错了几件事...

首先,单元格被重用(因此dequeueReusableCell),但是您的setupViews()函数每次单元格被重用时都会添加一个新的子视图

这意味着在滚动并重新使用单元格时,最终会出现2、3、4 ...一打子视图,所有子视图都具有冲突的约束。

addSubview()移动到单元格中的通用初始化函数,因此视图仅创建并添加一次。

这也是您应该设置约束的地方。

要在设计应用程序时更改子视图的高度,您要更改子视图的高度约束上的.constant

这是您修改的代码。我在代码中添加了足够清晰的注释:

class HattoriTableViewCell: UITableViewCell {

    // the view to add as a subview
    let myView: UIView = {
        let v = UIView()
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()

    // the constraint we'll use for myView's height
    var myViewHeightConstraint: NSLayoutConstraint!

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

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        commonInit()
    }

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

    func commonInit() -> Void {

        // add the subview
        self.addSubview(myView)

        // constrain it to all 4 sides
        myView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
        myView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
        myView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
        myView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true

        // create the height constraint
        myViewHeightConstraint = myView.heightAnchor.constraint(equalToConstant: 1)

        // needs Priority less-than 1000 (default) to avoid breaking constraints
        myViewHeightConstraint.priority = UILayoutPriority.init(999)

        // activate it
        myViewHeightConstraint.isActive = true

    }

    func setupViews(he:CGFloat, color:UIColor) {

        // set myView's background color
        myView.backgroundColor = color

        // change myView's height constraint constant
        myViewHeightConstraint.constant = he

    }

}

class HattoriViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    lazy var tableView: UITableView = {
        let table = UITableView()
        table.backgroundColor = .white
        table.translatesAutoresizingMaskIntoConstraints = false
        table.register(HattoriTableViewCell.self, forCellReuseIdentifier: "cellId")
        table.dataSource = self
        table.delegate = self
        return table
    }()


    let arr:[Int:UIColor] = [345: UIColor.random, 422: .random, 23: .random, 344: .random,200: .random,140: .random]

    var pickerDataVisitLocation = [203: "Home", 204: "Hospital", 205: "Other"]

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .red

        self.view.addSubview(tableView)
        //
        tableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
        tableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
        tableView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
        tableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
        tableView.tableFooterView = UIView()

        // use a reasonable value -- such as the average of what you expect (if known)
        tableView.estimatedRowHeight = 200
    }
}

extension HattoriViewController {

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

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

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! HattoriTableViewCell

        let value:UIColor = Array(arr)[indexPath.row].value
        let key = Array(arr)[indexPath.row].key

        cell.setupViews(he: CGFloat(key), color: value)

        return cell
    }

    // NOT NEEDED
//  func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
//      return UITableView.automaticDimension
//  }
//
//  func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
//      return UITableView.automaticDimension
//  }

}

extension UIColor {
    static var random: UIColor {
        return UIColor(red: .random(in: 0...1),
                       green: .random(in: 0...1),
                       blue: .random(in: 0...1),
                       alpha: 1.0)
    }
}

答案 2 :(得分:0)

我遇到了同样的问题,但出于不同的原因,我想与您分享。

我只是覆盖 layoutSubviews() 方法来添加我的自定义布局。

但后来我没有在初始化程序中调用 layoutIfNeeded() 方法来激活它们,而是仅在单元格出列和重用时才激活布局。

如果您遇到同样的问题,这里是我的代码供您参考:

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)

    contentView.addSubviews(containerView)
    containerView.addSubviews(titleLabel, subtitleLabel, activteSwitch)

    layoutIfNeeded() // Required to triger the overriden layoutSubviews() upon initialization
}

// However, I shouldn't override this method or add any constraints here
override func layoutSubviews() {
    let margin: CGFloat = 8

    containerView.snapToEdges()
    NSLayoutConstraint.activate([
        activteSwitch.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -margin),
        activteSwitch.centerYAnchor.constraint(equalTo: containerView.centerYAnchor),

        titleLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: margin),
        titleLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: margin),
        titleLabel.trailingAnchor.constraint(equalTo: activteSwitch.leadingAnchor, constant: -margin),

        subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: margin),
        subtitleLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: margin),
        subtitleLabel.trailingAnchor.constraint(equalTo: activteSwitch.leadingAnchor, constant: -margin),
        subtitleLabel.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -margin)

    ])
}