Convert Visual Format Language greater than to Constraint - swift

How would I convert this into a constraint?
"V:|->=0-[contentView]->=0-|"
Would it look like this?
contentView.topAnchor.constraint(lessThanOrEqualTo: topAnchor, constant: 0.0)
contentView.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: 0.0)

Since you haven't specified which view is the superview of contentView, we'll have to use contentView.superview! for the containing view. Therefore, your visual format is equivalent to:
contentView.topAnchor.constraint(greaterThanOrEqualTo: contentView.superview!.topAnchor, constant: 0.0)
contentView.bottomAnchor.constraint(lessThanOrEqualTo: contentView.superview!.bottomAnchor, constant: 0.0)
Notes:
If contentView has already been added as a subview of another view (which you should always do before creating constraints), then force unwrapping contentView.superview is safe.
contentView will start lower on the screen than its superview and the coordinates grow in the downward direction, so its offset will be larger making the constraint greaterThanOrEqualTo the superview's topAnchor.
Likewise, contentView will end above or touching its superview's bottomAnchor, so the constant in this case will be lessThanOrEqualTo 0.

Related

What is a good way to think of these layout contraints

I am learning about layout constraints and find it a bit confusing why the last line of NSLayout Constraints for the trailing anchor mentions a view instead of loginView? Is there any good logical way to think of this? Struggling to imagine what is written.
let loginView = LoginView()
view.addSubview(loginView)
NSLayoutConstraint.activate([
loginView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
loginView.leadingAnchor.constraint(equalToSystemSpacingAfter: view.leadingAnchor, multiplier: 1),
view.trailingAnchor.constraint(equalToSystemSpacingAfter: loginView.trailingAnchor, multiplier: 1)
])
To clarify the "flipping" between:
loginView.leadingAnchor.constraint(...)
and:
view.trailingAnchor.constraint(...)
Both of these sets of constraints will give the same result:
NSLayoutConstraint.activate([
loginView.heightAnchor.constraint(equalToConstant: 120.0),
loginView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
loginView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8.0),
loginView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -8.0),
])
NSLayoutConstraint.activate([
loginView.heightAnchor.constraint(equalToConstant: 120.0),
loginView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
loginView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8.0),
view.trailingAnchor.constraint(equalTo: loginView.trailingAnchor, constant: 8.0),
])
In each case, we're telling auto-layout to put the trailing-edge of loginView 8-points from the trailing-edge of view.
Which approach to use really comes down to individual preference: Do I like using all Positive values, with order-flipping? Or do I like using Positive values for "left-side" constraints and Negative values for "right-side" constraints without order-flipping (obviously, flip the terminology for LTR locales).
Starting with iOS 11, Apple added the concept of system spacing - which changes based on device size, accessibility options, etc - which we can use instead of hard-coded values.
We have equalToSystemSpacingAfter (and equalToSystemSpacingBelow), but we do not have equalToSystemSpacingBefore (or equalToSystemSpacingAbove).
So, if we want to use system spacing, we must "flip" the constraint order:
NSLayoutConstraint.activate([
loginView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
loginView.heightAnchor.constraint(equalToConstant: 120.0),
loginView.leadingAnchor.constraint(equalToSystemSpacingAfter: view.leadingAnchor, multiplier: 1),
view.trailingAnchor.constraint(equalToSystemSpacingAfter: loginView.trailingAnchor, multiplier: 1),
])
The code you posted is defining a set of layout constraints for the loginView object. The constraints specify how the loginView should be positioned within its parent view.
In the last line of the code, the view.trailingAnchor is being used as the reference for the trailing edge of the loginView. This means that the loginView will be positioned such that its trailing edge is aligned with the trailing edge of the parent view.
In general, when working with layout constraints, it is important to think about the relationship between the views being constrained and the constraints themselves. In this case, the loginView is the view being constrained, and the constraints are defining how the loginView should be positioned relative to its parent view.
view means self.view. This is a UIViewController; it has a view. This is the view that will contain the loginView; you can actually see the loginView being added to the view controller's view as a subview, right there in the code.
So this code inserts the loginView into self.view and then proceeds to describe the physical relationship between their sizes and positions.

Swift - How to set spacing between variable number of buttons in Horizontal StackView?

I'm using a horizontal UIStackView that can contain up to 3 UIButtons. (could be just 1, could be 2, or 3)
I want to have them centered, with the same space between each of them, like so:
I don't seem to be able to pull this off... I've tried every combination of Distribution and Alignment, but I can’t get it right.
Here's the stackView's constraints:
fileprivate func setupStackViewConstraints() {
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalToConstant: 60),
stackView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
stackView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)
])
}
And here's the buttons' constraints:
fileprivate func setupFacebookButton() {
let facebookButton = UIButton()
facebookButton.setImage(UIImage(named: "facebookSocialIcon"), for: .normal)
facebookButton.imageView?.contentMode = .scaleAspectFit
facebookButton.addTarget(self, action: #selector(facebookButtonWasTapped), for: .touchUpInside)
// -----------------------------------
facebookButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
facebookButton.widthAnchor.constraint(equalToConstant: 40),
facebookButton.heightAnchor.constraint(equalToConstant: 40)
])
// -----------------------------------
stackView.addArrangedSubview(facebookButton)
}
Any ideas?
The distribution and alignment are obvious here. You want "Equal Spacing" and "Center". You should also set a suitable spacing.
The "hard bit" is how to get the arranged views to horizontally align in the middle, since the alignment property is only about alignment perpendicular to axis.
The key is to let the stack view have a flexible width, and align it horizontally in the center. This means removing the leading and trailing constraints, and adding a center X constraint.
Therefore, these are the constraints on your stack view:
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalToConstant: 60),
stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
stackView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)
])
Note that the stack view will extend its width to fit all the arranged subviews in it. This means that if you have too many arranged subviews, some of them might go offscreen. If you want them to "wrap" to the next line, consider using a UICollectionView instead :)
Try constraining your StackView to center x and y only (not constraining it's leading and trailing anchors):
NSLayoutConstraint.activate([
stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
And then you can leave the distribution value to it's default and setting the spacing value to what you want:
stackView.spacing = // 16px
This way the StackView's width will be equal to the content width.

Weird transition of UILabel in modal during device orientation change

I'm using autolayout constraints to make an UILabel stay at the top of a modal.
title.topAnchor.constraint(equalTo: view.topAnchor, constant: 10)
title.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor, constant: 5)
title.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor, constant: -5)
title.centerXAnchor.constraint(equalTo: view.centerXAnchor)
The modal is presented at the center of the screen and its preferredContentSize is hardcoded as 312 width and 219 height.
However, on my iPhone 11, when my device is being rotated from portrait to landscape, the modal is whiteout firstly, and the UILabel flies into the view before rotation completes(img 1). But when my device is being rotated from landscape back to portrait, the UILabel rotates in place with the screen and never leaves the modal(img 2). Why there is such difference? What can I do to always make the UILabel rotates in place during the orientation change?
I feel like it might be related to my constraints but I couldn't figure out why. Could someone please help shed some light? Thanks!
Your constraints seem fine, you just have to activate those constraints.
NSLayoutConstraint.activate([
title.topAnchor.constraint(equalTo: view.topAnchor, constant: 10)
title.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor, constant: 5)
title.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor, constant: -5)
title.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])

Add layoutMargins to one element in a UIStackView

I would like to create a vertical stackview with 3 elements in it.
I want a bit more space only between the 2nd and the last element. So I thought about adding to the last element :
mylastelement.layoutMargins = UIEdgeInsets(top:30, left:0,bottom:0, right:0)
But the layoutmargins are not applied in my stackview. Is there any easy way to achieve that (Id like to avoid to modify the last element inner height).
EDIT : I just tried to increase 2nd element height (+50) within its frame by doing :
my2ndElementLabel.sizeToFit()
my2ndElementLabel.frame = CGRect(x:my2ndElementLabel.frame.origin.x,y:lmy2ndElementLabel.frame.origin.y,
width:my2ndElementLabel.frame.width, height:my2ndElementLabel.frame.height + 50)
but it has no effect.
EDIT2 : I tried to add a random view to my UIStackView, but the the view is just ignored ! May have missed something in understanding how UIKit work ?... :
let v = UIView(frame:CGRect(x:0,y:0,width:100,height:400))
v.backgroundColor = .red
myStackView.addArrangedSubview(v)
//...
Here is an extension I made that helps to achieve fast such margins :
extension UIStackView {
func addArrangedSubview(_ v:UIView, withMargin m:UIEdgeInsets )
{
let containerForMargin = UIView()
containerForMargin.addSubview(v)
v.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
v.topAnchor.constraint(equalTo: containerForMargin.topAnchor, constant:m.top ),
v.bottomAnchor.constraint(equalTo: containerForMargin.bottomAnchor, constant: m.bottom ),
v.leftAnchor.constraint(equalTo: containerForMargin.leftAnchor, constant: m.left),
v.rightAnchor.constraint(equalTo: containerForMargin.rightAnchor, constant: m.right)
])
addArrangedSubview(containerForMargin)
}
}
What you could do is set a custom spacing between the second and third element.
myStackView.setCustomSpacing(30.0, after: my2ndElementLabel)
In the same general vein, you can constrain the top (or bottom) anchor of your view relative to the corresponding edge of any view in which it's embedded. What's ugly being somewhat a matter of taste, I find autolayout constraints easy to use and easy to reason about.
A simple example from Mac OS rather than iOS:
let button = ControlFactory.labeledButton("Filter")
addSubview(button)
button.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20).isActive = true
button.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
This particular code lives in the view initializer, and positions a button in the middle of a view, 20 points up from the bottom.
I found myself : It looks like UIStackView doesn't work at all with old sizing system (with .frame). It seems you have to constraint height and width, and StackView will constraint left/top/right/bottom position for you when you add the arrangedSubview.
My second view was a label : I wanted a margin of 40, under the text. So i first computed the label height into its .frame property, and constraint the height at frame.height + 40(= my margin)
labelDesc.sizeToFit()
labelDesc.heightAnchor.constraint(equalToConstant:40).isActive = true
I find my own solution utterly ugly though. I'm sure UIKit provide a better way to achieve such a simple goal, without having to make these kind of DIY solutions. So please if you're used to work with UIKit, tell me if there is any better solution.
Consider adding a "margin" by inserting a correctly-sized UIView within the Stack View as needed.
If you need a 40px margin between 2 specific elements... add a UIView with a height constraint of 40px. Assign a clearColor background to make it invisible.
You can add IBOutlets to this view and hide it as you would any other item in the Stack View.

How can I avoid specifying potentially conflicting constraints

I am trying to create all of my views in my UICollectionViewCell programatically. The views currently lay out correctly.
I have a vertical stackView that contains two UILabels and a UIImageView.
The views do not have a fixed content size and are all variable.
If you see in my code, I am setting the leading and trailing constraints of my UIStackView with a constant of 8.0 from the safeAreaLayoutGuide. Because of this, I have to set the widthAnchor to be -16.0 (otherwise the cell does not lay out correctly). Is there a way I can avoid specifying this in two places? For example, if I set the leading to be 14.0 points from the leading, then I will have to make sure I remember to change my width to be -20.0, otherwise the contents will spill over.
override func updateConstraints() {
/// Check if views already have constraints
if !viewsHaveConstraints {
viewsHaveConstraints = true
/*
Here I set the hugging and compression priority for the 3 views in the UIStackView
*/
let margins = safeAreaLayoutGuide
/* Set vstackView constraints */
/// vStackView width anchor equal to width of margin less constant
vStackView.widthAnchor.constraint(equalTo: margins.widthAnchor, constant: -16.0).isActive = true
/// vStackView leading anchor constraint
vStackView.leadingAnchor.constraint(equalTo: margins.leadingAnchor, constant: 8.0).isActive = true
/// vStackView trailing anchor constraint
vStackView.trailingAnchor.constraint(equalTo: margins.trailingAnchor, constant: 8.0).isActive = true
vStackView.heightAnchor.constraint(equalTo: margins.heightAnchor).isActive = true
}
super.updateConstraints()
}