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