Anchoring view components programmatically gives error - swift

I have 2 buttons on the screen, the lower one is anchored to the view's bottom anchor, and the one above that is anchored to the lower button's top anchor. However, this throws an error "'Unable to activate constraint with anchors and because they have no common ancestor. Does the constraint or its anchors reference items in different view hierarchies? That's illegal.'
I have already added both buttons to the view (view.addSubView) and set translatesAutoresizingMaskIntoConstraints = false, no help.
view.addSubview(topButton)
topButton.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0).isActive = true
topButton.heightAnchor.constraint(equalToConstant: 60).isActive = true
topButton.widthAnchor.constraint(equalToConstant: 180).isActive = true
topButton.bottomAnchor.constraint(equalTo: lowerButton.topAnchor, constant: 120).isActive = true
view.addSubview(lowerButton)
lowerButton.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0).isActive = true
lowerButton.heightAnchor.constraint(equalToConstant: 60).isActive = true
lowerButton.widthAnchor.constraint(equalToConstant: 220).isActive = true
lowerButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -50).isActive = true
What I want for to happen is the lower button anchored to the view's bottom anchor, and the top button's bottom anchor anchored to the lower button's top anchor

You are adding constraints involving lowerButton before you actually add it to the view hierarchy. Simply move view.addSubview(lowerButton) to right below adding the topButton.

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.

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

How to keep pageControl in place while scrolling in a UICollectionView?

I've pinned my UIPageControl at the bottom of the collection view controller using this function in viewDidLoad..
func setUpViewsAndConstraints(){
collectionView.addSubview(pageControl)
collectionView.bringSubviewToFront(pageControl)
pageControl.centerXAnchor.constraint(equalTo: collectionView.centerXAnchor).isActive = true
pageControl.bottomAnchor.constraint(equalTo: collectionView.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
}
but when I scroll through the cells (horizontally) the pageControl stays under the first cell and is left behind when I move on to the next cell...
I thought that just cells moved when scrolling and collectionView stay in place but it seems it's not the case, or I'm doing something wrong..
what am I missing here?
thank you in advance for the answers!
I advice you to keep the pageControl under the collectionView and not inside, as I can see you are adding a bottomAnchor at your pageControl to the same bottom of the collectionView; try to keep them separately and add a top constraint to your pageControl to the bottom of collectionView and add a centralAnchor to your ViewController
pageControl.centerXAnchor.constraint(equalTo: viewController.centerXAnchor).isActive = true
pageControl.topAnchor.constraint(equalTo: collectionView.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
And maybe a bottom constraint to your view
pageControl.bottomAnchor.constraint(equalTo: viewController.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
Obviously for this case you should have a UIViewController parent with a UICollectionView and a pageControl as children