I have a UIStackView defined in storyboard with the first button's height set to 70 and other one set to 45. I get this autolayout error:
[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:0x280f614f0 UIButton:0x10641a120.height == 45 (active)>",
"<NSLayoutConstraint:0x280f60e60 UIButton:0x106418f80.height == 70 (active)>",
"<NSLayoutConstraint:0x280f604b0 'UISV-alignment' UIButton:0x10641a120.bottom == UIButton:0x106418f80.bottom (active)>",
"<NSLayoutConstraint:0x280f63cf0 'UISV-alignment' UIButton:0x10641a120.top == UIButton:0x106418f80.top (active)>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x280f60e60 UIButton:0x106418f80.height == 70 (active)>
I understand the UIStackView is unable to accept different heights of UIButtons, is that correct and what is the way to have UIStackView accept different heights or widths of it's elements?
Something in your Stack View constraints is causing the problem.
Here is a valid layout:
With the Stack View properties:
The result before adding a third button via code:
And the result after adding a third button (height constraint of 60) via code:
No auto-layout warnings or errors.
The code (connected to Button 1), adds / removes Button 3 as an arranged subview of the stack view:
class TestViewController: UIViewController {
#IBOutlet var theStackView: UIStackView!
var thirdButton: UIButton = {
let b = UIButton()
b.translatesAutoresizingMaskIntoConstraints = false
b.setTitle("Button 3", for: .normal)
b.backgroundColor = .red
return b
}()
#IBAction func doAddThird(_ sender: Any) {
if theStackView.arrangedSubviews.count == 2 {
theStackView.addArrangedSubview(thirdButton)
} else {
if let v = theStackView.arrangedSubviews.last {
v.removeFromSuperview()
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// finish initializing the third button
if let v = theStackView.arrangedSubviews.first as? UIButton {
thirdButton.titleLabel?.font = v.titleLabel?.font
}
NSLayoutConstraint.activate([
thirdButton.heightAnchor.constraint(equalToConstant: 60),
])
}
}
A Stackview will take on the dimensions of its components, if you don't give it height and width constraints. It looks like you are telling your stackView to be a particular height (either because you have a height constraint or two Y position constraints which imply a height). You cannot both tell the stackView to have a height and tell all of its arrangedSubviews to have a height unless those two values are exactly the same. So for instance if you tell the stack to be 150 high, and your buttons are 45 and 70 high then the one with the lowest content hugging priority loses and gets expanded to take up the extra 35 points of space that the stack view needs to be 150 points high.
Quick solutions:
Remove the hieght constraint on the stack view; it will now be as high as the sum of the high of its elements.
Add a blank UIView to the stack and give it content hugging of 1 and it will take up any extra space (this only works if your stack is bigger than its elements; if its too small you need to reduce the size of the arrangedSubviews instead).
Related
Given the view hierarchy:
UIStackView
--UILabel
--UISwitch
The label breaks too early, even if it can be fit to a single line.
Setting numberOfLines = 1 forces the label to be laid out correctly.
How to make UILabel perform line break only when needed?
Code:
private lazy var title: UILabel = {
let v = UILabel()
v.numberOfLines = 0
return v
}()
private lazy var toggle = UISwitch()
private lazy var stack = UIStackView(axis: .horizontal,
distribution: .equalSpacing,
alignment: .center,
views: [title,
toggle])
func setupConstraints() {
stack.snp.makeConstraints { (make) in
make.edges.equalTo(contentView.layoutMarginsGuide)
}
}
Result:
Setting numberOfLines = 1 gets me what I'd like to achieve, but the label looses its multi-line functionality:
How to force the desired behavior without losing support for multi-line labels?
When there is a lot of horizontal space, the label gets laid out correctly no matter of the numberOfLines property:
Set your UISwitch's content hugging and resistance priority to 1000.
And stack view distribution and alignment to fill.
Extra Note - If you want label and switch to be top aligned, then set alignment to top.
In your stack view you can give a constraint to your label and switch...
1) give your label leading, top , trailing and bottom constraint. Don't give Width constraint. In trailing constraint take second item Switch.
2) give your switch trailing, top, bottom and Fix width.
Hope it Will work.
Add label inside another stack view.
UIStackView
--UIStackView
--UILabel
--UISwitch
I'm coding a social network, which can permit to like and comment pictures.
However, I have a problem with that part ...
If I have one (or more) like and one (or more) comment, it's ok:
If I have one (or more) comment but no like, the user interface is not very good ...
I would like to move the button "2 comments" on the left when there is no like. I thought to do that by giving 0 width to the "1 like" button. However, the width it's not all the time the same. If I have 1 like or 12543 likes, the width will be different.
I have no idea about how I could correct this problem.
Do you have any idea please ?
you can create a custom component that will have a view, and two uilabels as a subviews if the view. the first uilabel will contain the number and the second the title ("comments").
the constraints of the uilables should be as follow:
first table leading == view.leading, first label trailing == second label leading, second label.trailing == view.trailing. view.height == "some constant". In this case the width of the view will be according to the labels width.
Now you should add those custom views to your view. In your case it will be two or one custom components, one for the likes and one for the comments.
add those custom view to tmprorary array and do the following code:
var prevLayoutedView : UIView = self
for (index, customView) in custumViewsArr.enumerated()
{
customView.leadingAnchor.constraint(equalTo: prevLayoutedView.leadingAnchor)
if index == custumViewsArr.enumerated.count
{
customView.trailingAnchor.constraint(equalTo: prevLayoutedView.trailing)
}
}
you will need to adjust some constants and compression resistance and hugging priority but this should assist you to solve it
UPDATE:
you can use less generic solution and not using a custom component but just using uilable. and then use the same for loop
first easy solution is to keep single label for both if not clickable
otherwise with two labels,
my constraints for two label is,
1. Constraints for like label:
2. Constraints for comment label
#IBOutlet var cnLeadingToView: NSLayoutConstraint!
#IBOutlet var cnLeadingToLikelbl: NSLayoutConstraint!
assign this outlet in storyboard for leading constraints of comment label then do this
if cntLikes == 0 { // temp variable
//hide the like lable
self.lblLikes.isHidden = true
self.cnLeadingToLikelbl.isActive = false // deactivate the leading constraint with like label
self.cnLeadingToView.isActive = true // activate the leading constraint with view
self.cnLeadingToView.constant = 15 // give leading space constant
}else {
self.lblLikes.isHidden = false
self.cnLeadingToLikelbl.isActive = true
self.cnLeadingToView.isActive = false
}
As #dahiya_boy said, I had to change the like label relationship from equalTo to lessthanequalto.
I put 375 by default and 0 if the is no like. It was that simple.
Thank you very much everybody !
I have tableView cell with different content(views, labels, imageViews) in one cell. But in something cells content can be not full. How can i use resizing cells without removing and adding always constraints? Thanks.
One of possible solutions for this problem:
Add constraints for hidden state with priority 1000
Add extra constraints for resized state with lower priority (ex 750)
Save constraints that is ONLY for hidden state into IBOutlet collection
Save constraints that is ONLY for resized state into another IBOutlet collection
Code:
#IBOutlet var hiddenConstraints: [NSLayoutConstraint] = []
#IBOutlet var visibleConstraints: [NSLayoutConstraint] = []
func hide(_ hide: Bool) {
for hiddenConstraint in self.hiddenConstraints {
hiddenConstraint.isActive = hide
}
for visibleConstraint in self.visibleConstraints {
visibleConstraint.isActive = !hide
}
self.layoutIfNeeded()
}
There is faster solution:
Move content that can be hidden into container view
Set height constraint for container view
Change from code height constraint constant to 0 if hidden or to proper height if visible
Code:
#IBOutlet var heightConstraint: NSLayoutConstraint!
func hide(_ hide: Bool) {
self. heightConstraint.constant = hide ? 0 : 150 //Estimated height
self.layoutIfNeeded()
}
This is not a good approach, as it will lead to constraint crashes at runtime. So I prefer to use first one.
Also you will need to update your cell from table to move other cells up or down.
Ray Wenderlich has a fantastic tutorial on dynamic sizing of table cells that can be found here:
https://www.raywenderlich.com/129059/self-sizing-table-view-cells
TL;DR You need to make sure your cell's content is pinned on all four sides to the cell's content view, as well as setting as high priority vertical hugging, greater than or equal to height constraint on your label.
I have a complex view hierarchy, built in Interface Builder, with nested UIStackViews. I get "unsatisfiable constraints" notices every time I hide some of my inner stackviews. I've tracked it down to this:
(
"<NSLayoutConstraint:0x1396632d0 'UISV-canvas-connection' UIStackView:0x1392c5020.top == UILabel:0x13960cd30'Also available on iBooks'.top>",
"<NSLayoutConstraint:0x139663470 'UISV-canvas-connection' V:[UIButton:0x139554f80]-(0)-| (Names: '|':UIStackView:0x1392c5020 )>",
"<NSLayoutConstraint:0x139552350 'UISV-hiding' V:[UIStackView:0x1392c5020(0)]>",
"<NSLayoutConstraint:0x139663890 'UISV-spacing' V:[UILabel:0x13960cd30'Also available on iBooks']-(8)-[UIButton:0x139554f80]>"
)
Specifically, the UISV-spacing constraint: when hiding a UIStackView its high constraint gets a 0 constant, but that seems to clash with the inner stackview's spacing constraint: it requires 8 points between my Label and Button, which is irreconcilable with the hiding constraint and so the constraints crash.
Is there a way around this? I've tried recursively hiding all the inner StackViews of the hidden stack view, but that results in strange animations where content floats up out of the screen, and causes severe FPS drops to boot, while still not fixing the problem.
This is a known problem with hiding nested stack views.
There are essentially 3 solutions to this problem:
Change the spacing to 0, but then you'll need to remember the previous spacing value.
Call innerStackView.removeFromSuperview(), but then you'll need to remember where to insert the stack view.
Wrap the stack view in a UIView with at least one 999 constraint. E.g. top#1000, leading#1000, trailing#1000, bottom#999.
The 3rd option is the best in my opinion. For more information about this problem, why it happens, the different solutions, and how to implement solution 3, see my answer to a similar question.
So, you have this:
And the problem is, when you first collapse the inner stack, you get auto layout errors:
2017-07-02 15:40:02.377297-0500 nestedStackViews[17331:1727436] [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:0x62800008ce90 'UISV-canvas-connection' UIStackView:0x7fa57a70fce0.top == UILabel:0x7fa57a70ffb0'Top Label of Inner Stack'.top (active)>",
"<NSLayoutConstraint:0x62800008cf30 'UISV-canvas-connection' V:[UILabel:0x7fa57d30def0'Bottom Label of Inner Sta...']-(0)-| (active, names: '|':UIStackView:0x7fa57a70fce0 )>",
"<NSLayoutConstraint:0x62000008bc70 'UISV-hiding' UIStackView:0x7fa57a70fce0.height == 0 (active)>",
"<NSLayoutConstraint:0x62800008cf80 'UISV-spacing' V:[UILabel:0x7fa57a70ffb0'Top Label of Inner Stack']-(8)-[UILabel:0x7fa57d30def0'Bottom Label of Inner Sta...'] (active)>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x62800008cf80 'UISV-spacing' V:[UILabel:0x7fa57a70ffb0'Top Label of Inner Stack']-(8)-[UILabel:0x7fa57d30def0'Bottom Label of Inner Sta...'] (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.
The problem, as you noted, is that the outer stack view applies a height = 0 constraint to the inner stack view. This conflicts with the 8 point padding constraint applied by the inner stack view between its own subviews. Both constraints cannot be satisfied simultaneously.
The outer stack view uses this height = 0 constraint, I believe, because it looks better when animated than just letting the inner view be hidden without shrinking first.
There's a simple fix for this: wrap the inner stack view in a plain UIView, and hide that wrapper. I'll demonstrate.
Here's the scene outline for the broken version above:
To fix the problem, select the inner stack view. From the menu bar, choose Editor > Embed In > View:
Interface Builder created a width constraint on the wrapper view when I did this, so delete that width constraint:
Next, create constraints between all four edges of the wrapper and the inner stack view:
At this point, the layout is actually correct at runtime, but Interface Builder draws it incorrectly. You can fix it by setting the vertical hugging priorities of the inner stack's children higher. I set them to 800:
We haven't actually fixed the unsatisfiable constrain problem at this point. To do so, find the bottom constraint that you just created and set its priority to less than required. Let's change it to 800:
Finally, you presumably had an outlet in your view controller connected to the inner stack view, because you were changing its hidden property. Change that outlet to connect to the wrapper view instead of the inner stack view. If your outlet's type is UIStackView, you'll need to change it to UIView. Mine was already of type UIView, so I just reconnected it in the storyboard:
Now, when you toggle the wrapper view's hidden property, the stack view will appear to collapse, with no unsatisfiable constraint warnings. It looks virtually identical, so I won't bother posting another GIF of the app running.
You can find my test project in this github repository.
I hit a similar problem with UISV-hiding. For me, the solution was to reduce the priorities of my own constraints from Required (1000) to something less than that. When UISV-hiding constrains are added, they take priority and the constraints no longer clash.
Ideally we could just set the priority of the UISV-spacing constraint to a lower value, but there doesn't appear to be any way to do that. :)
I am having success setting the spacing property of the nested stack views to 0 before hiding, and restoring to the proper value after making it visible again.
I think doing this recursively on nested stack views would work. You could store the original value of the spacing property in a dictionary and restore it later.
My project only has a single level of nesting, so I am unsure if this would result in FPS problems. As long as you don't animate the changes in spacing, I don't think it would create too much of a hit.
Another approach
Try to avoid nested UIStackViews. I love them and build almost everything with them. But as I recognized that they secretly add constraints I try to only use them at the highest level and non-nested where possible. This way I can specify the 2nd highest priority .defaultHighto the spacing constraint which resolves my warnings.
This priority is just enough to prevent most layout issues.
Of course you need to specify some more constraints but this way you have full control of them and make your view layout explicit.
Here's implementation of Senseful's suggestion #3 written as Swift 3 class using SnapKit constraints. I also tried overriding the properties, but never got it working without warnings, so I'll stick with wrapping UIStackView:
class NestableStackView: UIView {
private var actualStackView = UIStackView()
override init(frame: CGRect) {
super.init(frame: frame);
addSubview(actualStackView);
actualStackView.snp.makeConstraints { (make) in
// Lower edges priority to allow hiding when spacing > 0
make.edges.equalToSuperview().priority(999);
}
}
convenience init() {
self.init(frame: CGRect.zero);
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func addArrangedSubview(_ view: UIView) {
actualStackView.addArrangedSubview(view);
}
func removeArrangedSubview(_ view: UIView) {
actualStackView.removeArrangedSubview(view);
}
var axis: UILayoutConstraintAxis {
get {
return actualStackView.axis;
}
set {
actualStackView.axis = newValue;
}
}
open var distribution: UIStackViewDistribution {
get {
return actualStackView.distribution;
}
set {
actualStackView.distribution = newValue;
}
}
var alignment: UIStackViewAlignment {
get {
return actualStackView.alignment;
}
set {
actualStackView.alignment = newValue;
}
}
var spacing: CGFloat {
get {
return actualStackView.spacing;
}
set {
actualStackView.spacing = newValue;
}
}
}
In my case I was adding width and height constraint to a navigation bar button, as per the advice above I only added lower priority to the constraints.
open func customizeNavigationBarBackButton() {
let _selector = #selector(UIViewController._backButtonPressed(_:))
let backButtonView = UIButton(type: .custom)
backButtonView.setImage(UIImage(named: "icon_back"), for: .normal)
backButtonView.imageEdgeInsets = UIEdgeInsets.init(top: 0, left: -30, bottom: 0, right: 0)
backButtonView.snp.makeConstraints { $0.width.height.equalTo(44).priority(900) }
backButtonView.addTarget(self, action: _selector, for: .touchUpInside)
let backButton = UIBarButtonItem(customView: backButtonView)
self.navigationItem.leftBarButtonItem = backButton
}
Say I have three UILabels whose positions are like below:
[Label1] [Label2]
[Label3]
Label1 and Label2 are in the same row and Label3 is below them. All the labels will have a fixed width and will contain dynamic text, so their height will vary.
How do I make the Label3 10 points below the label which has a higher height using AutoLayout?
For example, if Label1's height is 100 points, Label2's height is 120 points (their Y positions are the same), then Label3 should be 10 points below Label2, but if Label1 is 120 points high and Label2 is 100 points high, then Label3 should be 10 points below Label1.
You simply make constraints between both Label3->Label1 and Label3->Label2. Use inequality constraints. There will be only one way to satisfy both!
You will also need a top constraint for Label3; its constant should be very small and its priority should be very low. This will give the two inequality constraints something to "aim at".
Here is an example. This as achieved entirely without code - the buttons have code to add text to the labels, of course, but the constraints are configured entirely in Interface Builder; the labels are resizing, and the bottom label is moving down, automatically. (You can construct the same layout in code if you want to, naturally.)
I suggest you to wrap top two labels to UIView and setup constraints so these labels fit all space inside that view. Then you simple add vertical spacing constraint to bottom label3 with constant = 10. In that case top view will have size of larger label and will satisfy your conditions
I thought this would be an interesting exercise so I create a little test project. The gist of the code is below. You can just copy/paste it in the standard Single View iOS template.
(Note that I use SnapKit for programmatic Auto Layout because it is so much simpler than the UIKit API. I find it even much simpler than doing things in Xcode.)
The result is exactly the same as Matt's great screencast.
// ViewController.swift
import UIKit
import SnapKit
class ViewController: UIViewController
{
override func viewDidLoad() {
super.viewDidLoad()
let leftLabel = UILabel()
leftLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "addText:"))
leftLabel.userInteractionEnabled = true
view.addSubview(leftLabel)
leftLabel.numberOfLines = 0
leftLabel.text = "All the world's a stage, and all the men and women merely players: they have their exits and their entrances; and one man in his time plays many parts, his acts being seven ages."
leftLabel.snp_makeConstraints { (make) -> Void in
make.top.equalTo(40)
make.left.equalTo(self.view)
make.right.equalTo(self.view.snp_centerX)
}
let rightLabel = UILabel()
rightLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "addText:"))
rightLabel.userInteractionEnabled = true
view.addSubview(rightLabel)
rightLabel.numberOfLines = 0
rightLabel.text = "There is a tide in the affairs of men, Which taken at the flood, leads on to fortune. Omitted, all the voyage of their life is bound in shallows and in miseries. On such a full sea are we now afloat. And we must take the current when it serves, or lose our ventures."
rightLabel.snp_makeConstraints { (make) -> Void in
make.top.equalTo(40)
make.right.equalTo(self.view)
make.left.equalTo(self.view.snp_centerX)
}
let bottomView = UIView()
view.addSubview(bottomView)
bottomView.backgroundColor = UIColor.redColor()
bottomView.snp_makeConstraints { (make) -> Void in
make.height.equalTo(20)
make.left.right.equalTo(self.view)
make.top.greaterThanOrEqualTo(leftLabel.snp_bottom)
make.top.greaterThanOrEqualTo(rightLabel.snp_bottom)
}
}
#objc func addText(recognizer: UIGestureRecognizer) {
if let label = recognizer.view as? UILabel {
label.text = label.text! + " I like cheese."
}
}
}
Updated the code to add some additional text to the labels when tapped.
First of all remove height constraints and set all 3 labels vertical Content Compression Resistance Priority to 1000. This is the most important part.
Then add vertical space from Label3 to Label 1, and set instead of Equal, Greater Than or Equal with priority say 500. Add same space constraint to Label2.
Last add constraint from Label3 to Top = 0, but set priority to 1.