What is a good way to think of these layout contraints - swift

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.

Related

Autolayout with Navigation Item

I'm trying to pin my UIImageView to the top via Autolayout and don't quite understand how to pin it to the Navigation Item itself.
It turns out to the view itself
How to write this line correctly?
imageView.topAnchor.constraint(equalTo: view.topAnchor, constant: 10) = true
The navigation bar is automatically added as a part of the safe area. You need to pin the top of your view to the safeAreaLayoutGuide like this:
imageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10) = true
Same goes for the tab bar.

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)
])

Convert Visual Format Language greater than to Constraint

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.

How to add a ViewController in containerView programmatically?

I know how to add a ViewController to a container if using an outlet through interface builder. You just drag an outlet from container to ViewController and boom, everythings all set and done. But when it comes to doing it programatically, I can't find any answer on the net? Can anyone give me a sample code? Thanks in advance.
Let's say I have 2 View Controllers and 1 containerView. First Controller is the parent and inside is the container view and inside the containerView is the secondView Controller.
You need to first add constraints with parent view and container view, then you also need to add constraints to your container controller. Finally you should add the didMoveToParent in the end of setting all the constraints.
The example is as follows, you can do a similar thing in your case.
NSLayoutConstraint.activateConstraints([
containerView.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor, constant: 10),
containerView.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor, constant: -10),
containerView.topAnchor.constraintEqualToAnchor(view.topAnchor, constant: 10),
containerView.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor, constant: -10),
])
NSLayoutConstraint.activateConstraints([
controller.view.leadingAnchor.constraintEqualToAnchor(containerView.leadingAnchor),
controller.view.trailingAnchor.constraintEqualToAnchor(containerView.trailingAnchor),
controller.view.topAnchor.constraintEqualToAnchor(containerView.topAnchor),
controller.view.bottomAnchor.constraintEqualToAnchor(containerView.bottomAnchor)
])
controller.didMoveToParentViewController(self)
or you can also refer this: https://codedump.io/share/jVuaGlB85VtK/1/how-to-add-a-container-view-programmatically