UIStackView with custom constraints - "Unable to simultaneously satisfy constraints" when changing axis - ios

I've stumbled upon an issue with UIStackView.
It has to do with the case when there are some custom constraints between the UIStackView and its arranged subviews. When changing the stack view's axis and the custom constraints, the error message [LayoutConstraints] Unable to simultaneously satisfy constraints. appears in the console.
Here is a code example which you can paste in a new project to observe the issue:
import UIKit
public class ViewController: UIViewController {
private var stateToggle = false
private let viewA: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.accessibilityIdentifier = "viewA"
view.backgroundColor = .red
return view
}()
private let viewB: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.accessibilityIdentifier = "viewB"
view.backgroundColor = .green
return view
}()
private lazy var stackView: UIStackView = {
let stackView = UIStackView(arrangedSubviews: [viewA, viewB])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.distribution = .fill
stackView.alignment = .fill
stackView.accessibilityIdentifier = "stackView"
return stackView
}()
private lazy var viewAOneFourthOfStackViewHightConstraint = viewA.heightAnchor.constraint(equalTo: stackView.heightAnchor, multiplier: 0.25)
private lazy var viewAOneFourthOfStackViewWidthConstraint = viewA.widthAnchor.constraint(equalTo: stackView.widthAnchor, multiplier: 0.25)
public override func viewDidLoad() {
super.viewDidLoad()
view.accessibilityIdentifier = "rootView"
view.addSubview(stackView)
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
stackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
stackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
])
let button = UIButton(type: .system)
button.frame = .init(x: 50, y: 50, width: 100, height: 44)
button.setTitle("toggle layout", for: .normal)
button.tintColor = .white
button.backgroundColor = .black
button.addTarget(self, action: #selector(buttonTap), for: .touchUpInside)
view.addSubview(button)
updateConstraintsBasedOnState()
}
#objc func buttonTap() {
stateToggle.toggle()
updateConstraintsBasedOnState()
}
private func updateConstraintsBasedOnState() {
switch (stateToggle) {
case true:
stackView.axis = .horizontal
viewAOneFourthOfStackViewHightConstraint.isActive = false
viewAOneFourthOfStackViewWidthConstraint.isActive = true
case false:
stackView.axis = .vertical
viewAOneFourthOfStackViewWidthConstraint.isActive = false
viewAOneFourthOfStackViewHightConstraint.isActive = true
}
}
}
In this example we're making a stack view with two subviews. We want one of them to take up 25% percent of the area - so we have the constraints to achieve this (one for each orientation), but we need to be able to switch them accordingly. When the switch happens (from vertical to horizontal stack in this case), the error message appears:
[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:0x60000089ca00 stackView.leading == UILayoutGuide:0x6000012b48c0'UIViewSafeAreaLayoutGuide'.leading (active, names: stackView:0x7fe269d07b50 )>",
"<NSLayoutConstraint:0x60000089c9b0 stackView.trailing == UILayoutGuide:0x6000012b48c0'UIViewSafeAreaLayoutGuide'.trailing (active, names: stackView:0x7fe269d07b50 )>",
"<NSLayoutConstraint:0x60000089c730 viewA.width == 0.25*stackView.width (active, names: viewA:0x7fe269e14fd0, stackView:0x7fe269d07b50 )>",
"<NSLayoutConstraint:0x6000008ba990 'UISV-canvas-connection' stackView.leading == viewA.leading (active, names: stackView:0x7fe269d07b50, viewA:0x7fe269e14fd0 )>",
"<NSLayoutConstraint:0x6000008ba9e0 'UISV-canvas-connection' H:[viewA]-(0)-| (active, names: stackView:0x7fe269d07b50, viewA:0x7fe269e14fd0, '|':stackView:0x7fe269d07b50 )>",
"<NSLayoutConstraint:0x6000008bab20 'UIView-Encapsulated-Layout-Width' rootView.width == 375 (active, names: rootView:0x7fe269c111a0 )>",
"<NSLayoutConstraint:0x60000089ce60 'UIViewSafeAreaLayoutGuide-left' H:|-(0)-[UILayoutGuide:0x6000012b48c0'UIViewSafeAreaLayoutGuide'](LTR) (active, names: rootView:0x7fe269c111a0, '|':rootView:0x7fe269c111a0 )>",
"<NSLayoutConstraint:0x60000089cdc0 'UIViewSafeAreaLayoutGuide-right' H:[UILayoutGuide:0x6000012b48c0'UIViewSafeAreaLayoutGuide']-(0)-|(LTR) (active, names: rootView:0x7fe269c111a0, '|':rootView:0x7fe269c111a0 )>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x6000008ba990 'UISV-canvas-connection' stackView.leading == viewA.leading (active, names: stackView:0x7fe269d07b50, viewA:0x7fe269e14fd0 )>
Notice the constraint: "<NSLayoutConstraint:0x6000008ba9e0 'UISV-canvas-connection' H:[viewA]-(0)-| (active, names: stackView:0x7fe269d07b50, viewA:0x7fe269e14fd0, '|':stackView:0x7fe269d07b50 )>" which says "viewA's trailing should be same as stack view's trailing". This is not a valid constraint for the horizontal axis layout. But it's not a constraint I've added, it's something internal to the stack view (UISV-canvas-connection).
It seems to me that because we're activating/deactivating custom constraint and also the stack view does the same internally when switching axis - there's temporarily a conflict and an error.
Potential solutions / workarounds:
Workaround 1: Make the custom constraints have priority = 999. This is not a good workaround - not only because it's hacky, but also because in some cases it would result in other layout issues (e.g. when viewA has some internal layout requirements that conflict, such as subviews with required hugging priority).
Workaround 2: Remove the arranged views before the axis change and re-add them after the axis change. This works but it's also hacky and might be difficult in practice for some complicated cases.
Workaround 3: Don't use UIStackView at all - implement using regular UIView as a container and create required constraints as needed.
Any ideas if this should be considered a bug (and reported to Apple) and if there are other (better?) solutions?
For example is there a way to tell AutoLayout - "I'm about to change some constraints and also the axis of a stack view - just wait until I'm done and then continue evaluating layout".

As I see it, both you and iOS have constraints that you need to deactivate and activate. In order to do it correctly and avoid conflicts, both you and iOS need to deactivate your old constraints before you can start to activate your new constraints.
Here's a solution that works. Tell the stackView to layoutIfNeeded() after you have deactivated your old constraint. Then it will get its constraints in order and you can activate your new constraint.
private func updateConstraintsBasedOnState() {
switch (stateToggle) {
case true:
stackView.axis = .horizontal
viewAOneFourthOfStackViewHightConstraint.isActive = false
stackView.layoutIfNeeded()
viewAOneFourthOfStackViewWidthConstraint.isActive = true
case false:
stackView.axis = .vertical
viewAOneFourthOfStackViewWidthConstraint.isActive = false
stackView.layoutIfNeeded()
viewAOneFourthOfStackViewHightConstraint.isActive = true
}
}

While vacawama's answer will do the job, two other options...
1 - Give the constraints a Priority less than .required to allow auto-layout to temporarily break the constraint without complaint.
Add this in viewDidLoad():
viewAOneFourthOfStackViewHightConstraint.priority = .defaultHigh
viewAOneFourthOfStackViewWidthConstraint.priority = .defaultHigh
and leave updateConstraintsBasedOnState() as you originally had it, or
2 - Use different priority levels, and swap them instead of deactivating / activating constraints.
Add this in viewDidLoad():
viewAOneFourthOfStackViewHightConstraint.priority = .defaultHigh
viewAOneFourthOfStackViewWidthConstraint.priority = .defaultLow
viewAOneFourthOfStackViewHightConstraint.isActive = true
viewAOneFourthOfStackViewWidthConstraint.isActive = true
and change updateConstraintsBasedOnState() to this:
private func updateConstraintsBasedOnState() {
switch (stateToggle) {
case true:
stackView.axis = .horizontal
viewAOneFourthOfStackViewHightConstraint.priority = .defaultLow
viewAOneFourthOfStackViewWidthConstraint.priority = .defaultHigh
case false:
stackView.axis = .vertical
viewAOneFourthOfStackViewWidthConstraint.priority = .defaultLow
viewAOneFourthOfStackViewHightConstraint.priority = .defaultHigh
}
}
I don't know if changing priorities is more or less efficient than changing activations? But might be something to consider.

Related

Will attempt to recover by breaking constraint (but not sure why my constraints are wrong)

I'm working on making a custom list cell (collection view list cell), based on this article. I manually add the height of the the view in the cell, but I see the warnings below in the console of Xcode, and not sure which part to fix.
[LayoutConstraints] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
translatesAutoresizingMaskIntoConstraints)
(
"<NSAutoresizingMaskLayoutConstraint:0x281209220 h=--& v=--& liveTest.LiveChannelContentView:0x128c13430.height == 44 (active)>",
"<NSLayoutConstraint:0x2812371b0 UIView:0x128c136b0.height == 60 (active)>",
"<NSLayoutConstraint:0x2812372a0 V:|-(0)-[UIView:0x128c136b0] (active, names: '|':liveTest.LiveChannelContentView:0x128c13430 )>",
"<NSLayoutConstraint:0x2812372f0 UIView:0x128c136b0.bottom == liveTest.LiveChannelContentView:0x128c13430.bottom (active)>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x2812371b0 UIView:0x128c136b0.height == 60 (active)>
The code below is where I get this error message.
class LiveChannelContentView: UIView, UIContentView {
let contentsView = UIView()
lazy var titleLabel: UILabel = {
let label = UILabel()
label.text = ""
return label
}()
lazy var statusLabel: UILabel = {
let label = UILabel()
label.text = ""
return label
}()
lazy var symbolImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
return imageView
}()
var liveEvent: LiveEvent?
init(configuration: LiveChannelContentConfiguration) {
// Custom initializer implementation here.
super.init(frame: .zero)
print("this is the view height: \(self.bounds.height)") // -> I get 0.0 in here
setupAllViews()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupAllViews() {
addSubview(contentsView)
contentsView.addSubview(symbolImageView)
contentsView.addSubview(indicator)
contentsView.addSubview(titleLabel)
contentsView.addSubview(statusLabel)
contentsView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
contentsView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor),
contentsView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor),
contentsView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor),
contentsView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor),
contentsView.heightAnchor.constraint(equalToConstant: 60)
])
contentsView.backgroundColor = .yellow
symbolImageView.centerY(leading: contentsView.leadingAnchor, trailing: nil, parent: contentsView, paddingLeft: 0, paddingRight: 0, size: CGSize(width: 50, height: 50))
indicator.centerY(leading: contentsView.leadingAnchor, trailing: nil, parent: contentsView, paddingLeft: 0, paddingRight: 0, size: CGSize(width: 50, height: 50))
titleLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
titleLabel.leadingAnchor.constraint(equalTo: symbolImageView.trailingAnchor, constant: 8),
titleLabel.topAnchor.constraint(equalTo: symbolImageView.topAnchor),
titleLabel.trailingAnchor.constraint(equalTo: contentsView.trailingAnchor)
])
statusLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
statusLabel.leadingAnchor.constraint(equalTo: titleLabel.leadingAnchor),
statusLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor),
statusLabel.trailingAnchor.constraint(equalTo: titleLabel.trailingAnchor)
])
print("this is the view after setup: \(self.bounds.height)") // I also get 0.0 in here
}
}
So, to clarify where the LiveChannelContentView is, I just add the yellow background to the view.
There are two things I don't get. First, even Xcode tells me that
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x2812371b0 UIView:0x128c136b0.height == 60 (active)>
when I take a screenshot of the app and measure the yellow background UIView's height, it is still 60. I thought breaking constraint means that using other height constraints instead of 60, but is that wrong?
Another thing is I was curious where the
"<NSAutoresizingMaskLayoutConstraint:0x281209220 h=--& v=--& liveTest.LiveChannelContentView:0x128c13430.height == 44 (active)>"
is used in my code. I searched the file contains 44 in my workspace but I got nothing.
Not really sure, but I thought the height of 44 and 60 is applied to the same UIView and Xcode got rid of the 60's height anchor or something. However, when I delete the height anchor for the contentsView, contentsView.heightAnchor.constraint(equalToConstant: 60), the app crashed like below.
I also tried deleting the top or the bottom anchor of the contentsView, but it also crushed the app.
contentsView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor),
or
contentsView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor),
So can anyone tell me which constraint should I fix to get rid of the waring please?
Change this part
contentsView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
contentsView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor),
contentsView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor),
contentsView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor),
contentsView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor),
contentsView.heightAnchor.constraint(equalToConstant: 60)
])
to
contentsView.translatesAutoresizingMaskIntoConstraints = false
let con = contentsView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor)
con.priority = UILayoutPriority(rawValue: 999)
NSLayoutConstraint.activate([
contentsView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor),
contentsView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor),
contentsView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor),
con,
contentsView.heightAnchor.constraint(equalToConstant: 60)
])

Stackview Show hide constraints not making self sizing possible

I've made UI for cell in cell class below:
final class OptionTVCell: UITableViewCell {
fileprivate static let id = String(describing: OptionTVCell.self)
private var defaultTintColor: UIColor {
let color = AppConfiguration.sharedAppConfiguration.appTextColor
return Utility.hexStringToUIColor(hex: color ?? "ffffff")
}
// MARK: - Subviews
lazy private(set) var optionImageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.heightAnchor.constraint(equalToConstant: 24).isActive = true
imageView.setContentHuggingPriority(.defaultHigh, for: .horizontal)
imageView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
let widthConstraint = imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor)
widthConstraint.priority = UILayoutPriority(998)
widthConstraint.isActive = true
imageView.contentMode = .scaleAspectFit
imageView.tintColor = defaultTintColor
return imageView
}()
lazy private(set) var optionNameLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.setContentHuggingPriority(.defaultLow, for: .horizontal)
label.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
label.textColor = defaultTintColor
label.numberOfLines = 0
let fontSize: CGFloat = Constants.shared.IPHONE ? 14 : 20
label.font = UIFont(name: Utility.getFontName(), size: fontSize)
return label
}()
lazy private(set) var separatorLineView: UIView = {
let separator = UIView()
separator.translatesAutoresizingMaskIntoConstraints = false
let heightConstraint = separator.heightAnchor.constraint(equalToConstant: 1)
heightConstraint.priority = UILayoutPriority(999)
heightConstraint.isActive = true
separator.backgroundColor = defaultTintColor.withAlphaComponent(0.5)
return separator
}()
lazy private(set) var separatorContainerStackView: UIStackView = {
let verticalStackView = UIStackView()
verticalStackView.translatesAutoresizingMaskIntoConstraints = false
verticalStackView.axis = .vertical
verticalStackView.spacing = 10
verticalStackView.distribution = .fillProportionally
verticalStackView.alignment = .fill
return verticalStackView
}()
lazy private(set) var iconContainerStackView: UIStackView = {
let horizontalStackView = UIStackView()
horizontalStackView.translatesAutoresizingMaskIntoConstraints = false
horizontalStackView.axis = .horizontal
horizontalStackView.spacing = 16
horizontalStackView.distribution = .fill
horizontalStackView.alignment = .center
return horizontalStackView
}()
// MARK: - Init
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Life Cycle
override func setHighlighted(_ highlighted: Bool, animated: Bool) {
super.setHighlighted(highlighted, animated: animated)
let color = highlighted
? Utility.hexStringToUIColor(hex: AppConfiguration.sharedAppConfiguration.primaryHoverColor ?? "ffffff")
: defaultTintColor
optionImageView.tintColor = color
optionNameLabel.textColor = color
}
// MARK: - Setup
private func setupView() {
selectionStyle = .none
backgroundColor = .clear
contentView.backgroundColor = .clear
setupIconStackView()
setupSeparatorStackView()
}
private func setupSeparatorStackView() {
contentView.addSubview(separatorContainerStackView)
NSLayoutConstraint.activate([
separatorContainerStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
separatorContainerStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
separatorContainerStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8),
separatorContainerStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16)
])
separatorContainerStackView.addArrangedSubview(iconContainerStackView)
separatorContainerStackView.addArrangedSubview(separatorLineView)
}
private func setupIconStackView() {
iconContainerStackView.addArrangedSubview(optionImageView)
iconContainerStackView.addArrangedSubview(optionNameLabel)
let labelWidth = optionNameLabel.widthAnchor.constraint(greaterThanOrEqualTo: iconContainerStackView.widthAnchor, constant: -40)
labelWidth.isActive = true
}
// MARK: - Configure
}
Below is the tableView implementation for cell:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return CGFloat(cellHeight)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard
let cell = tableView.dequeueReusableCell(withIdentifier: OptionTVCell.id) as? OptionTVCell,
indexPath.row <= optionsArray.count-1,
let navItem = optionsArray[indexPath.row] as? OptionItem
else {
return UITableViewCell()
}
cell.separatorLineView.isHidden = navItem.hasSeparator == true ? false : true
if let imageName = navItem.pageIcon, !imageName.isEmpty {
if let optionImage = UIImage(named: imageName)?.withRenderingMode(.alwaysTemplate) {
cell.optionImageView.image = optionImage
cell.optionImageView.isHidden = false
} else {
let listImageStringURL = imageName.appending("?w=\(Utility.sharedUtility.getImageSizeAsPerScreenResolution(size: cell.optionImageView.frame.size.width)))&h=\(Utility.sharedUtility.getImageSizeAsPerScreenResolution(size: cell.optionImageView.frame.size.height))")
if let imageURL = URL(string: listImageStringURL) {
cell.optionImageView.af.setImage(
withURL: imageURL,
placeholderImage: nil,
filter: nil,
imageTransition: .crossDissolve(0.2)
)
}
}
} else {
cell.optionImageView.isHidden = true
}
cell.optionNameLabel.text = navItem.title?.uppercased()
return cell
}
As you can see the separator view and icon can be hidden or shown and modifies cell height accordingly. The issue is the self sizing does not work on initial dequeue but after scrolling it fixes itself.
Here is the runtime error thrown from autolayout:
1.
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:0x600002525810 'fittingSizeHTarget' UIStackView:0x7fafd1e8c330.width == 0 (active)>",
"<NSLayoutConstraint:0x600002524f50 'UISV-canvas-connection' UIStackView:0x7fafd1e8c330.leading == UIImageView:0x7fafd1e8c4c0.leading (active)>",
"<NSLayoutConstraint:0x600002524fa0 'UISV-canvas-connection' H:[UILabel:0x7fafd1e8aa30]-(0)-| (active, names: '|':UIStackView:0x7fafd1e8c330 )>",
"<NSLayoutConstraint:0x600002524ff0 'UISV-spacing' H:[UIImageView:0x7fafd1e8c4c0]-(16)-[UILabel:0x7fafd1e8aa30] (active)>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x600002524ff0 'UISV-spacing' H:[UIImageView:0x7fafd1e8c4c0]-(16)-[UILabel:0x7fafd1e8aa30] (active)>
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:0x600002500410 V:|-(8)-[UIStackView:0x7fafd1f47c90] (active, names: '|':UITableViewCellContentView:0x7fafd1f0cf90 )>",
"<NSLayoutConstraint:0x600002500820 UIStackView:0x7fafd1f47c90.bottom == UITableViewCellContentView:0x7fafd1f0cf90.bottom - 8 (active)>",
"<NSLayoutConstraint:0x600002500550 UIImageView:0x7fafd1f569f0.height == 24 (active)>",
"<NSLayoutConstraint:0x600002502120 'UISV-canvas-connection' V:[_UILayoutSpacer:0x60000399e2b0'UISV-alignment-spanner']-(0)-| (active, names: '|':UIStackView:0x7fafd1f5a1e0 )>",
"<NSLayoutConstraint:0x600002500370 'UISV-canvas-connection' UIStackView:0x7fafd1f5a1e0.centerY == UIImageView:0x7fafd1f569f0.centerY (active)>",
"<NSLayoutConstraint:0x600002500000 'UISV-canvas-connection' UIStackView:0x7fafd1f47c90.top == UIStackView:0x7fafd1f5a1e0.top (active)>",
"<NSLayoutConstraint:0x6000025d2260 'UISV-canvas-connection' V:[UIView:0x7fafd1f48db0]-(0)-| (active, names: '|':UIStackView:0x7fafd1f47c90 )>",
"<NSLayoutConstraint:0x6000025b2490 'UISV-spacing' V:[UIStackView:0x7fafd1f5a1e0]-(10)-[UIView:0x7fafd1f48db0] (active)>",
"<NSLayoutConstraint:0x600002502210 'UISV-spanning-boundary' _UILayoutSpacer:0x60000399e2b0'UISV-alignment-spanner'.bottom >= UIImageView:0x7fafd1f569f0.bottom (active)>",
"<NSLayoutConstraint:0x6000025f2cb0 'UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x7fafd1f0cf90.height == 43.5 (active)>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x600002500550 UIImageView:0x7fafd1f569f0.height == 24 (active)>
What I did:
I have added a few constraints and reduced priority as they were giving some errors when made hidden but they had fixed height/width. But errors are still thrown and self sizing still not working properly.
You should be able to get rid of your layout errors / warnings with a single change:
lazy private(set) var separatorContainerStackView: UIStackView = {
let verticalStackView = UIStackView()
verticalStackView.translatesAutoresizingMaskIntoConstraints = false
verticalStackView.axis = .vertical
verticalStackView.spacing = 10
// use .fill NOT .fillProportionally
verticalStackView.distribution = .fill // .fillProportionally
verticalStackView.alignment = .fill
return verticalStackView
}()
A tip: when working with UIStackView, forget about the .fillProportionally distribution setting. There are very specific layouts where that is appropriate, but you are unlikely to encounter them. And, as you see, it can cause problems when used incorrectly.
As side notes:
lazy private(set) var optionImageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.heightAnchor.constraint(equalToConstant: 24).isActive = true
// you can use this
imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor).isActive = true
// none of this is needed
//imageView.setContentHuggingPriority(.defaultHigh, for: .horizontal)
//imageView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
//let widthConstraint = imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor)
//widthConstraint.priority = UILayoutPriority(998)
//widthConstraint.isActive = true
imageView.contentMode = .scaleAspectFit
imageView.tintColor = defaultTintColor
return imageView
}()
and:
lazy private(set) var optionNameLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
// these two are not needed
//label.setContentHuggingPriority(.defaultLow, for: .horizontal)
//label.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
label.textColor = defaultTintColor
label.numberOfLines = 0
let fontSize: CGFloat = Constants.shared.IPHONE ? 14 : 20
label.font = UIFont(name: Utility.getFontName(), size: fontSize)
return label
}()

adding subviews in uitableviewcell breaks constraints

I have an imageview with fixed height and width of 55 and a label under it but i get the following error. The constraints are not able to tell the tableview what the height should be even thought i set UITableView.automaticDimension
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.
(Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
(
"<NSAutoresizingMaskLayoutConstraint:0x2811767b0 h=--& v=--& UILabel:0x1057292b0'steve_rodriquez'.minY == 0 (active, names: '|':UITableViewCellContentView:0x105729520 )>",
"<NSAutoresizingMaskLayoutConstraint:0x281176800 h=--& v=--& UILabel:0x1057292b0'steve_rodriquez'.height == 0 (active)>",
"<NSLayoutConstraint:0x2811765d0 UILabel:0x1057292b0'steve_rodriquez'.bottom == UITableViewCellContentView:0x105729520.bottom (active)>",
"<NSLayoutConstraint:0x2811772a0 'UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x105729520.height == 0.333333 (active)>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x2811765d0 UILabel:0x1057292b0'steve_rodriquez'.bottom == UITableViewCellContentView:0x105729520.bottom (active)>
My tableviewcell class is this with the constraints
class ChatTableViewCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(userImageView)
self.contentView.addSubview(username)
userImageView_constraints()
username_constraints()
}
var userImageView: UIImageView = {
let imageView = UIImageView()
let imageViewHeightAndWidth: CGFloat = 55
let image = UIImage(named: "steve")
imageView.image = image
imageView.clipsToBounds = true
imageView.layer.cornerRadius = imageViewHeightAndWidth / 2
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
var username: UILabel = {
let label = UILabel()
label.text = "steve_rodriquez"
label.textAlignment = .center
label.font = UIFont(name: "Arial", size: 13)
return label
}()
func userImageView_constraints(){
userImageView.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 20).isActive = true
userImageView.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 0).isActive = true
userImageView.widthAnchor.constraint(equalToConstant: 55).isActive=true
userImageView.heightAnchor.constraint(equalToConstant: 55).isActive=true
}
func username_constraints(){
username.topAnchor.constraint(equalTo: userImageView.bottomAnchor, constant: 0).isActive = true
username.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: 0).isActive=true
username.centerXAnchor.constraint(equalTo: userImageView.centerXAnchor).isActive=true
username.widthAnchor.constraint(equalToConstant: 65).isActive=true
}
You forgot to set translatesAutoresizingMaskIntoConstraints = false on the label — as the error message quite plainly points out.

Autolayout error on height anchor when animation constraints

I have a requirement to render a UIView at the footer of my app, when the user takes some sort of action, that view scrolls to the top of the view and fixes in position.
Imagine almost a tab bar view animating into a navigation bar.
I have essentially mocked this out by have 2 anchors for my view, top and bottom.
I then toggle these anchors and use UIView.animate with layoutIfNeeded to move the position.
The contents of my component are then anchored to the safe area of the parent, meaning I avoid the top and bottom issues on the X series.
However when my view starts to animate I am getting an autolayout error in my terminal
(
"<NSLayoutConstraint:0x600002e38ff0 UILabel:0x7fe64bc04570'Foo Bar Boo Baz'.height == 44 (active)>",
"<NSLayoutConstraint:0x600002e38d70 UILabel:0x7fe64bc04570'Foo Bar Boo Baz'.top == UILayoutGuide:0x600003408d20'UIViewSafeAreaLayoutGuide'.top (active)>",
"<NSLayoutConstraint:0x600002e38e10 UILabel:0x7fe64bc04570'Foo Bar Boo Baz'.bottom == UILayoutGuide:0x600003408d20'UIViewSafeAreaLayoutGuide'.bottom (active)>",
"<NSLayoutConstraint:0x600002e39130 UIView:0x7fe64bc166b0.bottom == Home.AnimatedCustomNavigationView:0x7fe64bc02970.bottom (active)>",
"<NSLayoutConstraint:0x600002e390e0 V:|-(0)-[UIView:0x7fe64bc166b0] (active, names: '|':Home.AnimatedCustomNavigationView:0x7fe64bc02970 )>",
"<NSLayoutConstraint:0x600002e41810 'UIView-Encapsulated-Layout-Height' Home.AnimatedCustomNavigationView:0x7fe64bc02970.height == 896 (active)>",
"<NSLayoutConstraint:0x600002e38cd0 'UIViewSafeAreaLayoutGuide-bottom' V:[UILayoutGuide:0x600003408d20'UIViewSafeAreaLayoutGuide']-(34)-| (active, names: '|':UIView:0x7fe64bc166b0 )>",
"<NSLayoutConstraint:0x600002e38c30 'UIViewSafeAreaLayoutGuide-top' V:|-(0)-[UILayoutGuide:0x600003408d20'UIViewSafeAreaLayoutGuide'] (active, names: '|':UIView:0x7fe64bc166b0 )>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x600002e38ff0 UILabel:0x7fe64bc04570'Foo Bar Boo Baz'.height == 44 (active)>
Which appears to caused by the height anchor.
I am not sure how I can achieve this effect and also fix this error.
import UIKit
class AnimatedCustomNavigationView: UIView {
private lazy var navBar = UIView(frame: .zero)
private var navBarTopAnchor: NSLayoutConstraint!
private var navBarBottomAnchor: NSLayoutConstraint!
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = theme.color(.background)
navBar.backgroundColor = .purple
let label = UILabel(frame: .zero)
label.text = "Foo Bar Boo Baz"
label.sizeToFit()
addSubview(navBar)
navBar.addSubview(label)
[navBar, label].forEach { $0.translatesAutoresizingMaskIntoConstraints = false }
NSLayoutConstraint.activate([
// MARK: - NavBar Container
navBar.leadingAnchor.constraint(equalTo: self.leadingAnchor),
navBar.trailingAnchor.constraint(equalTo: self.trailingAnchor),
// MARK: - Label
label.topAnchor.constraint(equalTo: navBar.safeAreaLayoutGuide.topAnchor),
label.leadingAnchor.constraint(equalTo: navBar.leadingAnchor, constant: 16),
label.bottomAnchor.constraint(equalTo: navBar.safeAreaLayoutGuide.bottomAnchor),
label.trailingAnchor.constraint(equalTo: navBar.trailingAnchor, constant: -16),
label.heightAnchor.constraint(equalToConstant: 44)
])
navBarTopAnchor = navBar.topAnchor.constraint(equalTo: topAnchor)
navBarTopAnchor.isActive = false
navBarBottomAnchor = navBar.bottomAnchor.constraint(equalTo: bottomAnchor)
navBarBottomAnchor.isActive = true
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.navBarTopAnchor.isActive = true
self.navBarBottomAnchor.isActive = false
UIView.animate(withDuration: 1.5, delay: 0, options: .curveEaseInOut, animations: {
self.layoutIfNeeded()
}, completion: nil)
}
}
required init?(coder aDecoder: NSCoder) {
return nil
}
}
To simulate a user action for now, I have wrapped my animation call in a DispatchQueue.main.asyncAfter
You need to correctly order the .isActive = false should be before .isActive = true
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.navBarBottomAnchor.isActive = false
self.navBarTopAnchor.isActive = true
UIView.animate(withDuration: 1.5, delay: 0, options: .curveEaseInOut, animations: {
self.layoutIfNeeded()
}, completion: nil)
}

UIStackView spacing breaks constraints when not in set updateConstraints

I have a simple ViewController with a custom subview called MyView:
import UIKit
class ViewController: UIViewController {
let myView = MyView()
override func viewDidLoad() {
super.viewDidLoad()
myView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(myView)
myView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -8.0).isActive = true
myView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8.0).isActive = true
myView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -8.0).isActive = true
}
}
class MyView: UIView {
let stackView: UIStackView
let label = UILabel()
let label2 = UILabel()
init() {
stackView = UIStackView(arrangedSubviews: [label, label2])
super.init(frame: .zero)
stackView.axis = .vertical
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "Label 1"
label2.translatesAutoresizingMaskIntoConstraints = false
label2.text = "Label 2"
stackView.spacing = 8.0
addSubview(stackView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func updateConstraints() {
super.updateConstraints()
stackView.fillSuperview()
}
}
When I run this, I get the following breaking constraints in the console:
2018-03-23 16:11:58.960493+0100 ViewWithStackView[75612:10756278] [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:0x608000280f00 'UISV-canvas-connection' UIStackView:0x7f8438e07510.top == UILabel:0x7f8438d06740'Label 1'.top (active)>",
"<NSLayoutConstraint:0x608000280fa0 'UISV-canvas-connection' V:[UILabel:0x7f8438e07790'Label 2']-(0)-| (active, names: '|':UIStackView:0x7f8438e07510 )>",
"<NSLayoutConstraint:0x608000280ff0 'UISV-spacing' V:[UILabel:0x7f8438d06740'Label 1']-(8)-[UILabel:0x7f8438e07790'Label 2'] (active)>",
"<NSLayoutConstraint:0x608000280550 'UIView-Encapsulated-Layout-Height' UIStackView:0x7f8438e07510.height == 0 (active)>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x608000280ff0 'UISV-spacing' V:[UILabel:0x7f8438d06740'Label 1']-(8)-[UILabel:0x7f8438e07790'Label 2'] (active)>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
2018-03-23 16:11:58.961737+0100 ViewWithStackView[75612:10756278]
[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.
(Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
(
"<NSAutoresizingMaskLayoutConstraint:0x6080002812c0 h=--& v=--& UIStackView:0x7f8438e07510.height == 0 (active)>",
"<NSLayoutConstraint:0x608000280f00 'UISV-canvas-connection' UIStackView:0x7f8438e07510.top == UILabel:0x7f8438d06740'Label 1'.top (active)>",
"<NSLayoutConstraint:0x608000280fa0 'UISV-canvas-connection' V:[UILabel:0x7f8438e07790'Label 2']-(0)-| (active, names: '|':UIStackView:0x7f8438e07510 )>",
"<NSLayoutConstraint:0x608000280ff0 'UISV-spacing' V:[UILabel:0x7f8438d06740'Label 1']-(8)-[UILabel:0x7f8438e07790'Label 2'] (active)>"
)
Will attempt to recover by breaking constraint
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
Now if I move the following line stackView.spacing = 8.0 to the updateConstraints method, the constraints won't break. So the following code works fine without warning / error in the console:
import UIKit
class ViewController: UIViewController {
let myView = MyView()
override func viewDidLoad() {
super.viewDidLoad()
myView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(myView)
myView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -8.0).isActive = true
myView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8.0).isActive = true
myView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -8.0).isActive = true
}
}
class MyView: UIView {
let stackView: UIStackView
let label = UILabel()
let label2 = UILabel()
init() {
stackView = UIStackView(arrangedSubviews: [label, label2])
super.init(frame: .zero)
stackView.axis = .vertical
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "Label 1"
label2.translatesAutoresizingMaskIntoConstraints = false
label2.text = "Label 2"
addSubview(stackView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func updateConstraints() {
super.updateConstraints()
stackView.fillSuperview()
stackView.spacing = 8.0
}
}
Why does moving stackView.spacing = 8.0 to updateConstraints solve this issue / remove the warnings / errors?
Thank you for an explanation :)
It looks like this is the constraint you don't want
"<NSLayoutConstraint:0x608000280550 'UIView-Encapsulated-Layout-Height' UIStackView:0x7f8438e07510.height == 0 (active)>"
The first thing I'd try is
stackView.translatesAutoresizingMaskIntoConstraints = false
Also, I don't know what stackView.fillSuperview() is doing but you'd probably be better off adding constraints to it so fills the view.
In the first case the stackView has a zero height because you haven't set a frame for it ( in init before fillSuperview()), so setting a spacing of 8 between the 2 labels will cause a conflict , but in second the frame is zero and spacing is zero and the 2 labels will have a zero height according to (intrinsic content size) so, in this case no conflict will happen , then you set spacing in updateConstraints but at that moment stackView has a frame equal to it's superView according to this line fillSuperview() so that goes smoothly also

Resources