My cell looks like this :
class listofJobsTVC: UITableViewCell {
let jobName = UILabel()
let jobNumber = UILabel()
let jobDescription = UILabel()
let jobStartDate = UILabel()
let jobEndDate = UILabel()
let jobTaskCount = UILabel()
let expandBtn = UIButton()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
for view in contentView.subviews {
view.removeFromSuperview()
}
SetupCell()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func SetupCell() {
let jobNameLeft = UILabel()
let jobNumberLeft = UILabel()
let jobDescriptionLeft = UILabel()
let jobStartDateLeft = UILabel()
let jobEndDateLeft = UILabel()
let jobTaskCountLeft = UILabel()
let container = ShadowRoundedView()
addSubview(container)
addConstraintsWithFormat("H:|-15-[v0]-15-|", views: container)
addConstraintsWithFormat("V:|-15-[v0]-15-|", views: container)
container.addSubview(jobName)
container.addSubview(jobNumber)
container.addSubview(jobDescription)
container.addSubview(jobStartDate)
container.addSubview(jobEndDate)
container.addSubview(jobTaskCount)
container.addSubview(jobNameLeft)
container.addSubview(jobNumberLeft)
container.addSubview(jobDescriptionLeft)
container.addSubview(jobStartDateLeft)
container.addSubview(jobEndDateLeft)
container.addSubview(jobTaskCountLeft)
container.addSubview(expandBtn)
let leftWidth : CGFloat = 150
let rightBtnWidth : CGFloat = 30
container.addConstraintsWithFormat("V:|[v0]|", views: expandBtn)
container.addConstraintsWithFormat("V:|-10-[v0]-8-[v1]-8-[v2]-8-[v3]-8-[v4]-8-[v5]-10-|", views: jobName, jobNumber, jobDescription, jobStartDate, jobEndDate, jobTaskCount)
container.addConstraintsWithFormat("H:|-10-[v0(\(leftWidth))]-8-[v1]-10-[v2(\(rightBtnWidth))]|", views: jobNameLeft, jobName, expandBtn)
container.addConstraintsWithFormat("H:|-10-[v0(\(leftWidth))]-8-[v1]-10-[v2(\(rightBtnWidth))]|", views: jobNumberLeft, jobNumber, expandBtn)
container.addConstraintsWithFormat("H:|-10-[v0(\(leftWidth))]-8-[v1]-10-[v2(\(rightBtnWidth))]|", views: jobDescriptionLeft, jobDescription, expandBtn)
container.addConstraintsWithFormat("H:|-10-[v0(\(leftWidth))]-8-[v1]-10-[v2(\(rightBtnWidth))]|", views: jobStartDateLeft, jobStartDate, expandBtn)
container.addConstraintsWithFormat("H:|-10-[v0(\(leftWidth))]-8-[v1]-10-[v2(\(rightBtnWidth))]|", views: jobEndDateLeft, jobEndDate, expandBtn)
container.addConstraintsWithFormat("H:|-10-[v0(\(leftWidth))]-8-[v1]-10-[v2(\(rightBtnWidth))]|", views: jobTaskCountLeft, jobTaskCount, expandBtn)
jobNameLeft.topAnchor.constraint(equalTo: jobName.topAnchor).isActive = true
jobNumberLeft.topAnchor.constraint(equalTo: jobNumber.topAnchor).isActive = true
jobDescriptionLeft.topAnchor.constraint(equalTo: jobDescription.topAnchor).isActive = true
jobStartDateLeft.topAnchor.constraint(equalTo: jobStartDate.topAnchor).isActive = true
jobEndDateLeft.topAnchor.constraint(equalTo: jobEndDate.topAnchor).isActive = true
jobTaskCountLeft.topAnchor.constraint(equalTo: jobTaskCount.topAnchor).isActive = true
setUpLabel(AppColors.appPrimaryTealColor, jobName)
setUpLabel(AppColors.appPrimaryTealColor, jobNumber)
setUpLabel(AppColors.appPrimaryTealColor, jobDescription)
setUpLabel(AppColors.appPrimaryTealColor, jobStartDate)
setUpLabel(AppColors.appPrimaryTealColor, jobEndDate)
setUpLabel(AppColors.appPrimaryTealColor, jobTaskCount)
setUpLabel(.black, jobNameLeft)
setUpLabel(.black, jobNumberLeft)
setUpLabel(.black, jobDescriptionLeft)
setUpLabel(.black, jobStartDateLeft)
setUpLabel(.black, jobEndDateLeft)
setUpLabel(.black, jobTaskCountLeft)
jobNameLeft.text = "JOB NAME"
jobNumberLeft.text = "JOB NUMBER"
jobDescriptionLeft.text = "JOB DESCRIPTION"
jobStartDateLeft.text = "JOB START DATE"
jobEndDateLeft.text = "JOB END DATE"
jobTaskCountLeft.text = "JOB TASK COUNT"
expandBtn.roundCorners([.topRight,.bottomRight], radius: 15)
expandBtn.backgroundColor = AppColors.appPrimaryTealColor
expandBtn.setImage(#imageLiteral(resourceName: "arrow-down-sign-to-navigate").maskWithColor(color: .white), for: .normal)
expandBtn.imageView?.contentMode = .scaleAspectFit
expandBtn.imageEdgeInsets = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 5)
container.cornerRadious = 15
}
func setUpLabel(_ color : UIColor, _ label : UILabel){
label.font = UIFont.systemFont(ofSize: 16, weight: .semibold)
label.numberOfLines = 0
label.textColor = color
}
}
and in my view controller I am making my table have automatic dimension like this :
mainTable.backgroundColor = .clear
mainTable.separatorStyle = .none
mainTable.rowHeight = UITableView.automaticDimension
mainTable.dataSource = self
mainTable.delegate = self
and here is the cell for row method :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = listofJobsTVC()
cell.jobName.text = ": 1"
cell.jobNumber.text = ": 1"
cell.jobDescription.text = ": 1"
cell.jobStartDate.text = ": 1"
cell.jobEndDate.text = ": 1"
cell.jobTaskCount.text = ": 1"
return cell
}
the design of the cell comes out perfect but this weird constraint keeps breaking for each cell I create which is really annoying
2021-02-21 14:56:30.961185+0530 INDORE IPDS[30128:647175] SQLiteDB opened!
2021-02-21 14:56:34.325851+0530 INDORE IPDS[30128:647175] [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:0x600001363890 V:|-(15)-[INDORE_IPDS.ShadowRoundedView:0x7f806ec2a140] (active, names: '|':INDORE_IPDS.listofJobsTVC:0x7f806ec27880 )>",
"<NSLayoutConstraint:0x600001363840 V:[INDORE_IPDS.ShadowRoundedView:0x7f806ec2a140]-(15)-| (active, names: '|':INDORE_IPDS.listofJobsTVC:0x7f806ec27880 )>",
"<NSLayoutConstraint:0x600001363700 V:|-(10)-[UILabel:0x7f806ec10e60] (active, names: '|':INDORE_IPDS.ShadowRoundedView:0x7f806ec2a140 )>",
"<NSLayoutConstraint:0x6000013636b0 V:[UILabel:0x7f806ec10e60]-(8)-[UILabel:0x7f806ec27c50] (active)>",
"<NSLayoutConstraint:0x600001363660 V:[UILabel:0x7f806ec27c50]-(8)-[UILabel:0x7f806ec27ed0] (active)>",
"<NSLayoutConstraint:0x600001363610 V:[UILabel:0x7f806ec27ed0]-(8)-[UILabel:0x7f806ec28150] (active)>",
"<NSLayoutConstraint:0x6000013635c0 V:[UILabel:0x7f806ec28150]-(8)-[UILabel:0x7f806ec283d0] (active)>",
"<NSLayoutConstraint:0x600001363570 V:[UILabel:0x7f806ec283d0]-(8)-[UILabel:0x7f806ec28650] (active)>",
"<NSLayoutConstraint:0x600001363520 V:[UILabel:0x7f806ec28650]-(1000)-| (active, names: '|':INDORE_IPDS.ShadowRoundedView:0x7f806ec2a140 )>",
"<NSLayoutConstraint:0x6000013793b0 'UIView-Encapsulated-Layout-Height' INDORE_IPDS.listofJobsTVC:0x7f806ec27880.height == 44 (active)>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x600001363570 V:[UILabel:0x7f806ec283d0]-(8)-[UILabel:0x7f806ec28650] (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.
I have made cells this way a thousand times but never saw constraints break like this. I am guessing the 44 is default height of a Tableview cell. Also don't tell me to put estimated row height.. done that.. did not change anything also all the cells I created before works fine without estimated row height.. I dunno what to do .. I don't see any wrong constraints in the cell either..
For reference heres the addConstraintsWithFormat method :
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: [], metrics: nil, views: viewsDictionary))
}
This is a common issue...
As I understand it, auto-layout begins its process with the assumption that a row (cell) has the default height of 44. As it starts laying out the cell's UI elements, it can throw warnings because it has not yet finished the layout, and at that point there are conflicts.
That's why you get the warning / error messages, but the cell lays out as expected.
One way around this is to give the Bottom constraint a Priority of less-than-required.
You can do this by changing one line in your code from:
container.addConstraintsWithFormat("V:|-10-[v0]-8-[v1]-8-[v2]-8-[v3]-8-[v4]-8-[v5]-10-|", views: jobName, jobNumber, jobDescription, jobStartDate, jobEndDate, jobTaskCount)
to:
container.addConstraintsWithFormat("V:|-10-[v0]-8-[v1]-8-[v2]-8-[v3]-8-[v4]-8-[v5]-10@750-|", views: jobName, jobNumber, jobDescription, jobStartDate, jobEndDate, jobTaskCount)
Note the addition of "[v5]-10 @750 -|" for the last vertical constraint.
That allows auto-layout to "temporarily" break the constraint without filling the debug console with warning messages.
As a side note, it's my impression that VFL has been dropped by Apple. At least, I haven't seen anything new about it for several years. While it can work fine for simple layouts, it has many deficiencies and, in my view, is not nearly as clear or easy to debug when problems arise.
In addition, the way you've done this ends up applying both the Width and Trailing constraints to your expandBtn
6 times. While it has no adverse effect in this specific instance, it's pretty easy to accidentally add multiple constraints which end up having conflicts.
Here's another approach that I, personally, find a bit more logical and much easier to manage and debug:
let leftWidth : CGFloat = 150
let rightBtnWidth : CGFloat = 30
expandBtn.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// constrain expand button
// Top / Trailing / Bottom to container
expandBtn.topAnchor.constraint(equalTo: container.topAnchor, constant: 0.0),
expandBtn.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: 0.0),
expandBtn.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: 0.0),
// width = rightBtnWidth (constant value)
expandBtn.widthAnchor.constraint(equalToConstant: rightBtnWidth),
])
// arrays of left and right labels
let leftLabels: [UILabel] = [
jobNameLeft, jobNumberLeft, jobDescriptionLeft, jobStartDateLeft, jobEndDateLeft, jobTaskCountLeft,
]
let rightLabels: [UILabel] = [
jobName, jobNumber, jobDescription, jobStartDate, jobEndDate, jobTaskCount,
]
// this will be tracked inside the for loop
var prevLabel: UILabel!
for (leftLabel, rightLabel) in zip(leftLabels, rightLabels) {
// of course
leftLabel.translatesAutoresizingMaskIntoConstraints = false
rightLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// all left labels have Leading: 10.0 and Width: leftWidth
leftLabel.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 10.0),
leftLabel.widthAnchor.constraint(equalToConstant: leftWidth),
// all right labels have Leading: 8.0 from left label Trailing
rightLabel.leadingAnchor.constraint(equalTo: leftLabel.trailingAnchor, constant: 8.0),
// all right labels have Trailing: -10.0 from expand button Leading
rightLabel.trailingAnchor.constraint(equalTo: expandBtn.leadingAnchor, constant: -10.0),
// all right labels have Top equal to respective left label Top
rightLabel.topAnchor.constraint(equalTo: leftLabel.topAnchor, constant: 0.0),
])
if nil == prevLabel {
// if it's the First left label
// constrain Top to container Top + 10.0
leftLabel.topAnchor.constraint(equalTo: container.topAnchor, constant: 10.0).isActive = true
} else {
// not the first label, so
// constrain Top to previous label Bottom + 8.0
leftLabel.topAnchor.constraint(equalTo: prevLabel.bottomAnchor, constant: 8.0).isActive = true
}
if leftLabel == leftLabels.last {
// if it's the Last left label
// constrain Bottom to container Bottom - 10.0
let c = leftLabel.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: -10.0)
// also give it a less-than-required Priority, so auto-layout won't complain
c.priority = .defaultHigh
c.isActive = true
}
prevLabel = leftLabel
}
// this is no longer needed
/*
container.addConstraintsWithFormat("V:|[v0]|", views: expandBtn)
container.addConstraintsWithFormat("V:|-10-[v0]-8-[v1]-8-[v2]-8-[v3]-8-[v4]-8-[v5]-10@750-|", views: jobName, jobNumber, jobDescription, jobStartDate, jobEndDate, jobTaskCount)
container.addConstraintsWithFormat("H:|-10-[v0(\(leftWidth))]-8-[v1]-10-[v2(\(rightBtnWidth))]|", views: jobNameLeft, jobName, expandBtn)
container.addConstraintsWithFormat("H:|-10-[v0(\(leftWidth))]-8-[v1]-10-[v2(\(rightBtnWidth))]|", views: jobNumberLeft, jobNumber, expandBtn)
container.addConstraintsWithFormat("H:|-10-[v0(\(leftWidth))]-8-[v1]-10-[v2(\(rightBtnWidth))]|", views: jobDescriptionLeft, jobDescription, expandBtn)
container.addConstraintsWithFormat("H:|-10-[v0(\(leftWidth))]-8-[v1]-10-[v2(\(rightBtnWidth))]|", views: jobStartDateLeft, jobStartDate, expandBtn)
container.addConstraintsWithFormat("H:|-10-[v0(\(leftWidth))]-8-[v1]-10-[v2(\(rightBtnWidth))]|", views: jobEndDateLeft, jobEndDate, expandBtn)
container.addConstraintsWithFormat("H:|-10-[v0(\(leftWidth))]-8-[v1]-10-[v2(\(rightBtnWidth))]|", views: jobTaskCountLeft, jobTaskCount, expandBtn)
jobNameLeft.topAnchor.constraint(equalTo: jobName.topAnchor).isActive = true
jobNumberLeft.topAnchor.constraint(equalTo: jobNumber.topAnchor).isActive = true
jobDescriptionLeft.topAnchor.constraint(equalTo: jobDescription.topAnchor).isActive = true
jobStartDateLeft.topAnchor.constraint(equalTo: jobStartDate.topAnchor).isActive = true
jobEndDateLeft.topAnchor.constraint(equalTo: jobEndDate.topAnchor).isActive = true
jobTaskCountLeft.topAnchor.constraint(equalTo: jobTaskCount.topAnchor).isActive = true
*/