Expandable Uitableview Cell Using Autolayout Results in Uiviewalertforunsatisfiableconstraints

Expandable UITableView Cell using Autolayout results in UIViewAlertForUnsatisfiableConstraints

Setting the height constraint priority to 999 may feel like a hack, but according to Apple's docs:

NOTE Don’t feel obligated to use all 1000 priority values. In fact, priorities should general cluster around the system-defined low (250), medium (500), high (750), and required (1000) priorities. You may need to make constraints that are one or two points higher or lower than these values, to help prevent ties. If you’re going much beyond that, you probably want to reexamine your layout’s logic.

Probably a little less than intuitive:

"I want the button height to control the cell height, so lower its priority????"

But, that does appear to be the "correct" way to do it.

Ref: https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/AnatomyofaConstraint.html#//apple_ref/doc/uid/TP40010853-CH9-SW19

Autolayout constraint warnings

If you'll read the warning properly, you'll understand that you are setting an explicit height (55) on the view in the cell's contentView and you are constraining it to top and bottom of the contentView. Thus you are basically specifying the height of the cell using autolayout on its content.

To allow that, you should set rowHeight to UITableViewAutomaticDimension and estimatedRowHeight to a constant that estimates its height the best.

Then the UIView-Encapsulated-Layout-Height constraint in the warning tells you that the conflict emerges just because of the height of the cell that is set by the tableView based on the rowHeight/estimatedRowHeight - for more on this topic, read this question.

Now you have to decide whether you want to use the row height set throught tableView.rowHeight, or height specified by the autolayout constraints on cell's content. If you want to use autolayout, as I assume based on that constraints, use the following code.

Try using this code to setup the tableView:

private let tableView: SelfSizedTableView = {
let tv = SelfSizedTableView()
tv.isScrollEnabled = false
tv.translatesAutoresizingMaskIntoConstraints = false
tv.tableFooterView = UIView()

// explicitly set the row height and its estimated row height
tv.estimatedRowHeight = 55 // ideally use 55 + edgeInsets.top + edgeInsets.bottom
tv.rowHeight = UITableViewAutomaticDimension // automatic height based on content

tv.register(TestTableViewCell.self, forCellReuseIdentifier: "cellId")
return tv
}()

And then use this code to setup the cell's content view:

if view == nil {
view = UIView(frame: .zero)
view?.backgroundColor = .green
view?.translatesAutoresizingMaskIntoConstraints = false

contentView.addSubview(view!)

// set priority on one of the constraints to 999:
let heightConstraint = view!.heightAnchor.constraint(equalToConstant: 55)
heightConstraint.priority = UILayoutPriority(rawValue: 999)
// for reasoning behind this, read https://stackoverflow.com/q/44651241/2912282

NSLayoutConstraint.activate([
heightConstraint,
view!.topAnchor.constraint(equalTo: contentView.topAnchor, constant: edgeInsets.top),
view!.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -edgeInsets.bottom),
view!.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: edgeInsets.left),
view!.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -edgeInsets.right)
])
}

How to set auto layout correctly for a table cell with dynamic height?

  • If you want to avoid warning, try the code below

    class MyCellView: UITableViewCell {

    private let ImageView = UIImageView()
    private let titleView = UILabel()

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {

    super.init(style: style, reuseIdentifier: reuseIdentifier)

    contentView.addSubview(ImageView)
    contentView.addSubview(titleView)

    ImageView.translatesAutoresizingMaskIntoConstraints = false
    ImageView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
    ImageView.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true
    ImageView.rightAnchor.constraint(equalTo: contentView.rightAnchor).isActive = true
    let heightConstraint = ImageView.heightAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.6)
    heightConstraint.priority = UILayoutPriority.defaultLow
    heightConstraint.isActive = true

    titleView.translatesAutoresizingMaskIntoConstraints = false
    titleView.topAnchor.constraint(equalTo: ImageView.bottomAnchor).isActive = true
    titleView.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true
    titleView.rightAnchor.constraint(equalTo: contentView.rightAnchor).isActive = true
    titleView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true

    titleView.numberOfLines = 0

    // titleView.text = " "
    }
    }
  • If you remove the code about titleView entirely and want to have an UIImageView with dynamic height in the cell, adding bottomAnchor constraint for imageView

    class MyCellView: UITableViewCell {

    private let ImageView = UIImageView()
    private let titleView = UILabel()

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {

    super.init(style: style, reuseIdentifier: reuseIdentifier)

    contentView.addSubview(ImageView)
    contentView.addSubview(titleView)

    ImageView.translatesAutoresizingMaskIntoConstraints = false
    ImageView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
    ImageView.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true
    ImageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
    ImageView.rightAnchor.constraint(equalTo: contentView.rightAnchor).isActive = true
    let heightConstraint = ImageView.heightAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.6)
    heightConstraint.priority = UILayoutPriority.defaultLow
    heightConstraint.isActive = true
    }
    }

Unable to simultaneously satisfy constraints when update height constraints of imageView swift 4

Considering that the last constraint says

"<NSLayoutConstraint:0x608000289970 'UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x7f9cbae5a0c0.height == 476.5   (active)>"

I assume you are using stackView inside of a UITableViewCell to implement automatic height cells in a tableView. If my assumption is correct, then the problem is not with the stackView, nor with the imageView, but with the way UITableView works with UITableViewAutomaticDimension and Autolayout. If the layout works as you expect, and the warning is the only thing that bugs you, then read following.

Therefore it seems to me that this is a result of an known "bug" - there is a collision of the height set by the tableView and the height calculated by the autolayout. When rendering the cell, the tableView first applies the default height, calculates the autolayout height, and then use the latter - at least it seems so. See my question. The constraint mentioned above ('UIView-Encapsulated-Layout-Height') is the one applied by the UITableView that later goes away.

That means that the constraints you are using are probably OK. Just set one of the constraints defining height to priority = 999 (so that until it deactivates the default height constraint it won't cause any conflict). In the end, it will result in using your constraint anyway, so it will not cause any layout trouble.

E.g., if you constrain the stackView to fit the cell's contentView, set the stackView.bottomAnchor to contentView.bottomAnchor just with the priority set to 999. If you did the layout programmatically, this might be your solution:

let bottomConstraint = tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
bottomConstraint.priority = UILayoutPriority(rawValue: 999)
NSLayoutConstraint.activate([
// rest of the constraints
stackView.topAnchor.constraint(equalTo: contentView.topAnchor),
stackView.leftAnchor.constraint(equalTo: contentView.leftAnchor),
stackView.rightAnchor.constraint(equalTo: contentView.rightAnchor),
bottomConstraint,
])

If you do the layout in storyboards, just select appropriate constraint in the storyboards, and in the attributes inspector set its priority to 999 (for example):

Settings constraint's priority to 999

Label on Row Separator - Swift Tableview - Hourly Calendar

It's not possible (or technically, it would be possible, but the overhead is too high, considering your other options).

Instead of using cell separators, set separatorStyle = .none, and draw the line in the cell (e.g., as a UIView with view.height = 1 and view.backgroundColor = .grey) and normally add the label in the cell.

Basically the solution is very simple: disable standard separator lines, and rather draw separator inside the cell (bottom or top) along with the labels. That's how I've been doing things when the client asked for some custom fancy separators - I added a custom line at the bottom of the cell and used the rest of the cell's contentView as for the cell's content.

EDIT

You can use a following example to start with (note that this is just one of several different approaches how to manage it):

class TimeCellViewController: UITableViewController {
override func loadView() {
super.loadView()

// you can use UITableViewAutomaticDimension instead of static height, if
// there will be variable heights that you don't know upfront
// https://stackoverflow.com/a/18746930/2912282
// or mine:
// https://stackoverflow.com/a/47963680/2912282
tableView.rowHeight = 80
tableView.estimatedRowHeight = 80

tableView.separatorStyle = .none

// to allow scrolling below the last cell
tableView.tableFooterView = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 40))

tableView.register(TimeCell.self, forCellReuseIdentifier: "timeCell")
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 24
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "timeCell", for: indexPath) as! TimeCell
if indexPath.row > 0 {
cell.topTime = "\(indexPath.row):00"
} else {
cell.topTime = ""
}
cell.bottomTime = "\(indexPath.row + 1):00"
return cell
}
}

class TimeCell: UITableViewCell {
// little "hack" using two labels to render time both above and below the cell
private let topTimeLabel = UILabel()
private let bottomTimeLabel = UILabel()

private let separatorLine = UIView()

var topTime: String = "" {
didSet {
topTimeLabel.text = topTime
}
}
var bottomTime: String = "" {
didSet {
bottomTimeLabel.text = bottomTime
}
}

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

selectionStyle = .none

contentView.addSubview(topTimeLabel)
contentView.addSubview(bottomTimeLabel)
contentView.addSubview(separatorLine)

topTimeLabel.textColor = UIColor.gray
topTimeLabel.textAlignment = .right
bottomTimeLabel.textColor = UIColor.gray
bottomTimeLabel.textAlignment = .right
separatorLine.backgroundColor = UIColor.gray

bottomTimeLabel.translatesAutoresizingMaskIntoConstraints = false
topTimeLabel.translatesAutoresizingMaskIntoConstraints = false
separatorLine.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
bottomTimeLabel.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 0),
bottomTimeLabel.centerYAnchor.constraint(equalTo: self.bottomAnchor),
bottomTimeLabel.widthAnchor.constraint(equalToConstant: 50),
topTimeLabel.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 0),
topTimeLabel.centerYAnchor.constraint(equalTo: self.topAnchor),
topTimeLabel.widthAnchor.constraint(equalToConstant: 50),
separatorLine.leftAnchor.constraint(equalTo: bottomTimeLabel.rightAnchor, constant: 8),
separatorLine.bottomAnchor.constraint(equalTo: self.bottomAnchor),
separatorLine.heightAnchor.constraint(equalToConstant: 1),
separatorLine.rightAnchor.constraint(equalTo: self.rightAnchor, constant: 0),
])

// if you use UITableViewAutomaticDimension instead of static height,
// you will have to set priority of one of the height constraints to 999, see
// https://stackoverflow.com/q/44651241/2912282
// and
// https://stackoverflow.com/a/48131525/2912282
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Minimum cell height - Autolayout

Set the height constraint of the optional label as greater than or equal to whatever height you want. Also, set the compression resistance and content hugging of the label to required. Your label should now consume extra height if there is more content, or just take the minimum size that was set.

[label addConstraint:[NSLayoutConstraint constraintWithItem:label attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0 constant:15/*the min height you need*/]];
[label setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
[label setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];

UITableView when changing constraint which effects height of cell after dequeueing, end up with broken constraints

Set your constraints' priorities to 999. Just like now, it will display correctly.



Related Topics



Leave a reply



Submit