代码创建UITableViewCell时约束冲突问题求助
UITableViewCell 显示按钮后约束冲突问题解决
纯代码实现UITableViewCell,包含cardView和buttonOptions按钮。单独使用cardView时布局正常,显示buttonOptions后控制台出现约束冲突警告,注释self.buttonOptions.isHidden = false则警告消失。
完整代码:
import UIKit class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { private lazy var tableView: UITableView = { let view = UITableView() self.view.addSubview(view) view.translatesAutoresizingMaskIntoConstraints = false view.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true view.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true view.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor).isActive = true view.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor).isActive = true return view }() override func viewDidLoad() { super.viewDidLoad() tableView.register(Cell.self, forCellReuseIdentifier: "Cell") tableView.separatorStyle = .none tableView.delegate = self tableView.dataSource = self } func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 1 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell: Cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! Cell cell.configure() return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { print("selected: \(indexPath.row)") } } private final class Cell: UITableViewCell { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) self.backgroundColor = .clear self.selectionStyle = .none } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private lazy var cardView: UIView = { let view = UIView() self.contentView.addSubview(view) view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ view.leftAnchor.constraint(equalTo: self.contentView.leftAnchor, constant: 18), view.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 9), view.rightAnchor.constraint(equalTo: self.contentView.rightAnchor, constant: -18), view.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -9), view.heightAnchor.constraint(equalToConstant: 60) ]) view.layer.cornerRadius = 8 view.layer.borderColor = UIColor.lightGray.withAlphaComponent(0.7).cgColor view.layer.borderWidth = 1.0 return view }() private(set) lazy var buttonOptions: UIButton = { let button = UIButton() self.cardView.addSubview(button) button.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ button.widthAnchor.constraint(equalToConstant: 22), button.heightAnchor.constraint(equalToConstant: 18), button.centerXAnchor.constraint(equalTo: self.cardView.centerXAnchor), button.centerYAnchor.constraint(equalTo: self.cardView.centerYAnchor) ]) button.backgroundColor = UIColor.lightGray.withAlphaComponent(0.6) button.setImage(UIImage(systemName: "ellipsis")!, for: .normal) button.imageView?.tintColor = UIColor.lightGray return button }() func configure() { self.cardView.backgroundColor = .white self.buttonOptions.isHidden = false } }
报错信息:
[LayoutConstraints] Unable to simultaneously satisfy constraints. Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. ( "<NSLayoutConstraint:0x6000005b7480 V:|-(9)-[UIView:0x7ff5ac9066e0] (active, names: '|':UITableViewCellContentView:0x7ff5ac90d4d0 )>", "<NSLayoutConstraint:0x6000005b7520 UIView:0x7ff5ac9066e0.bottom == UITableViewCellContentView:0x7ff5ac90d4d0.bottom - 9 (active)>", "<NSLayoutConstraint:0x6000005b7570 UIView:0x7ff5ac9066e0.height == 60 (active)>", "<NSLayoutConstraint:0x60000059c3c0 'UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x7ff5ac90d4d0.height == 44 (active)>") Will attempt to recover by breaking constraint <NSLayoutConstraint:0x6000005b7570 UIView:0x7ff5ac9066e0.height == 60 (active)> Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger. The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful.
问题分析
冲突根源是UITableView默认行高(44pt)和cell内部约束所需高度不匹配:
- cardView要求上下各留9pt间距,自身高度60pt,因此cell内容视图需要的总高度是
60 + 9 + 9 = 78pt - UITableView默认给cell设置了
UIView-Encapsulated-Layout-Height约束(高度44pt),两者无法同时满足 - 隐藏buttonOptions时,视图布局未完全触发,UITableView暂时未严格计算高度,因此没有警告;显示按钮后布局完全生效,冲突暴露
解决方案
1. 开启UITableView自动行高
在ViewController的viewDidLoad方法中添加以下代码,让UITableView根据cell内部约束自动计算行高:
// 开启自动行高 tableView.rowHeight = UITableView.automaticDimension // 设置估计行高(接近实际高度即可,帮助UITableView优化滚动性能) tableView.estimatedRowHeight = 80
2. 优化cell内部约束(可选)
如果不需要cardView固定高度,可以删除其heightAnchor约束,让cardView的高度由内部控件(如buttonOptions)和上下间距自动撑开,布局更灵活:
修改cardView的约束代码,移除这一行:
view.heightAnchor.constraint(equalToConstant: 60)
修改后的完整代码示例
import UIKit class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { private lazy var tableView: UITableView = { let view = UITableView() self.view.addSubview(view) view.translatesAutoresizingMaskIntoConstraints = false view.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true view.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true view.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor).isActive = true view.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor).isActive = true return view }() override func viewDidLoad() { super.viewDidLoad() tableView.register(Cell.self, forCellReuseIdentifier: "Cell") tableView.separatorStyle = .none // 新增:开启自动行高 tableView.rowHeight = UITableView.automaticDimension tableView.estimatedRowHeight = 80 tableView.delegate = self tableView.dataSource = self } func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 1 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell: Cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! Cell cell.configure() return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { print("selected: \(indexPath.row)") } } private final class Cell: UITableViewCell { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) self.backgroundColor = .clear self.selectionStyle = .none } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private lazy var cardView: UIView = { let view = UIView() self.contentView.addSubview(view) view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ view.leftAnchor.constraint(equalTo: self.contentView.leftAnchor, constant: 18), view.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 9), view.rightAnchor.constraint(equalTo: self.contentView.rightAnchor, constant: -18), view.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -9), view.heightAnchor.constraint(equalToConstant: 60) ]) view.layer.cornerRadius = 8 view.layer.borderColor = UIColor.lightGray.withAlphaComponent(0.7).cgColor view.layer.borderWidth = 1.0 return view }() private(set) lazy var buttonOptions: UIButton = { let button = UIButton() self.cardView.addSubview(button) button.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ button.widthAnchor.constraint(equalToConstant: 22), button.heightAnchor.constraint(equalToConstant: 18), button.centerXAnchor.constraint(equalTo: self.cardView.centerXAnchor), button.centerYAnchor.constraint(equalTo: self.cardView.centerYAnchor) ]) button.backgroundColor = UIColor.lightGray.withAlphaComponent(0.6) button.setImage(UIImage(systemName: "ellipsis")!, for: .normal) button.imageView?.tintColor = UIColor.lightGray return button }() func configure() { self.cardView.backgroundColor = .white self.buttonOptions.isHidden = false } }
内容的提问来源于stack exchange,提问作者Youmate
相关产品推荐
相关产品推荐

