UIKit: Constraints with respecting NavBar and ignoring safe area at the bottom - ios

I'm following by one of the video of Paul Hudson and trying to recreate detail screen with full screen image. By following the video I set up constraints to Reset to suggested contains but I have different values compering to video. I tried to play around with settings but can't get the expected result...
Constraints:
Image View.top = topMargin - 44
Image View.centerX = centerX
Image View.centerY = centerY
Image View.leading = Safe Area.leading
Result:
Expected:
Question: How to set up constraints to respect NavigationBar and took all other place in the screen, like in expected image?

Assuming your VC is embedded in a navigation controller, you basically want to constraint the left, right and bottom of the image view to be equal to its superview, and the top of the image view to the top layout guide:
You should select the image view, and add the constraints using this pop up:
Click on all four of the thingys I circled. If the first or second items of the constraints added are incorrect, change them by selecting the constraint and using the drop down here:
Alternatively, just add them with code:
imageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
imageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
imageView.leftAnchor.constraint(equalTo: view.leftAnchor),
imageView.rightAnchor.constraint(equalTo: view.rightAnchor),
imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])

Based on answer from Sweeper created constraints in Storyboard:
Image View.leading = leading
Image View.top = Safe Area.top
trailing = Image View.trailing
bottom = Image View.bottom

Related

How to make a UITextView fill up the remaining space

I have a UIButton and UITextField side by side as follows.
I want the Add button to only have a width based on the content. And the text view shall take up the rest of the space. I have the following auto layout constraints.
private func setupLayout() {
newDeviceIdTextField.topAnchor.constraint(equalTo: deviceIdLabel.bottomAnchor, constant: 12).isActive = true
newDeviceIdTextField.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 12).isActive = true
newDeviceIdButton.topAnchor.constraint(equalTo: deviceIdLabel.bottomAnchor, constant: 12).isActive = true
newDeviceIdButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -12).isActive = true
newDeviceIdButton.leadingAnchor.constraint(equalTo: newDeviceIdTextField.trailingAnchor, constant: 0).isActive = true
}
In this article there is a section which I believe exactly solves my problem. But I just don't understand how.
What am I missing here?
A button automatically sizes its width to fit its content, and both a text field and a button have an automatic height. So what you want to do is trivial:
Pin the left of the text field where you want it.
Pin the right of the button where you want it.
Pin the tops of both where you want them.
Pin the right of the text field to the left of the button and set that constant to a small number such as 8.
You can do this without writing code, from storyboard. What i do for this kind of UI is,
Pin the left of the text field where you want it.
Pin the right of the button where you want it.
Pin the tops of both where you want them.
Pin the right of the text field to the left of the button
It's look like in image below
Now Select button > Size Inspector > In Content hugging Priority
Change Horizontal to 750(high) and you done :)
Result look like in image below

How Autolayout constraint First Item and Second Item works?

If I have UIView with top 100 left 120 right 120 how exactly First Item and Second Item relationship work. After going through Apple document each relationship has a linear equation with an expression as.
firstItem.firstItemAttribute == secondItem.secondItemAttribute * multiplier + constant
With this equation for top leading constraint with relation
First Item Safe Area.Leading
Relation =
Second Item = MyView.Leading
Constant 120
with equation the First Item And Second Item I understood. But Problem for me in understanding when I do the reverse the second item constant changes to - 120 and layout has no effect after the change. Why negative has no effect and what is its use for?
Xcode provides a set of properties for FirstItem & SecondItem like leading, center, trailing when we need to switch this properties.
First Item = Safe Area.Leading --- that's the "left edge" of the Safe Area
Second Item = MyView.Leading --- that's the "left edge" of your view
So, "left edge" of the Safe Area to "left edge" of your view equals 120, or, your view is 120 pts from the Safe Area (to the right).
First Item = MyView.Leading --- that's the "left edge" of your view
Second Item = Safe Area.Leading --- that's the "left edge" of the Safe Area
So, "left edge" of your view to "left edge" of the Safe Area equals -120, or, the left edge of Safe Area is -120 pts from your view (to the left).
If you change -120 to 120, that would put the left edge of the Safe Area +120 pts, or 120 pts to the right, of your view's left edge, which would push the view off the screen to the left.
Edit:
For a little more clarity...
With "SafeArea.Leading -> myView.Leading", you are saying "put myView's left edge 120-pts from the left edge of the Safe Area"
When you swap them to "myView.Leading -> SafeArea.Leading", you are saying "put SafeArea's left edge minus 120-pts from the left edge of myView".
In general, when using Interface Builder, you leave the order alone... because you are visually laying out your elements, and IB knows how to define the constraints.
More often where you will see a difference, is when setting up constraints in code.
For example, to put a subview (red) inside a containing view (blue), with 20-pts padding on all four sides like this:
your code might look like this:
let myContainerView = UIView()
myContainerView.translatesAutoresizingMaskIntoConstraints = false
myContainerView.backgroundColor = .blue
// add container view to self view
view.addSubview(myContainerView)
// constrain container view center X and Y, width and height both 240-pts
NSLayoutConstraint.activate([
myContainerView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0.0),
myContainerView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0.0),
myContainerView.widthAnchor.constraint(equalToConstant: 240.0),
myContainerView.heightAnchor.constraint(equalToConstant: 240.0),
])
let myInnerView = UIView()
myInnerView.translatesAutoresizingMaskIntoConstraints = false
myInnerView.backgroundColor = .red
// add inner view to container view
myContainerView.addSubview(myInnerView)
// constrain inner view with 20-pts padding on all four sides
NSLayoutConstraint.activate([
// top and left are 20-pts from superview top and left
myInnerView.topAnchor.constraint(equalTo:
myContainerView.topAnchor, constant: 20.0),
myInnerView.leadingAnchor.constraint(equalTo:
myContainerView.leadingAnchor, constant: 20.0),
// bottom and right are *minus* 20-pts from superview bottom and right
myInnerView.bottomAnchor.constraint(equalTo:
myContainerView.bottomAnchor, constant: -20.0),
myInnerView.trailingAnchor.constraint(equalTo:
myContainerView.trailingAnchor, constant: -20.0),
])
You will note that the trailing and bottom constraints of the inner view must be negative, because you want them to be 20-pts away from the trailing and bottom edges of the container view.

How can I get the height of a view if I set its anchors

I am currently trying to access the height of a view which I previously set anchors for. So for example, I set the left, right, top, and bottom anchors of searchTable using the following in my ViewController:
searchTable.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
searchTable.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
searchTable.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
searchTable.bottomAnchor.constraint(equalTo: menuBar.topAnchor).isActive = true
searchTable is an object of a class that I created that inherits from UIView and has a UIImageView in it. I constrain the UIImageView by using the following in the init function:
imageView.topAnchor.constraint(equalTo: topContainer.topAnchor, constant: 10).isActive = true
imageView.leftAnchor.constraint(equalTo: topContainer.leftAnchor, constant: 10).isActive = true
imageView.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: topContainerMultiplier * imageProfileMultiplier).isActive = true
imageView.widthAnchor.constraint(equalTo: self.heightAnchor, multiplier: topContainerMultiplier * imageProfileMultiplier).isActive = true
where:
let topContainerMultipler: Double = 1 / 7
let imageProfileMultipler: Double = 1 / 5
Inside the init function of searchTable, I try want to be able to set the corner radius to be half the image size. I tried to use self.frame.height, self.frame.size.height, self.bounds.height and also getting the .constant value of the self.heightAnchor constraint, but all returns 0.
I would think that there is a solution to get around this, but I haven't been able to find it.
You are querying the height of your view's frame after defining your constraints, but before layout has actually resolved those constraints into a frame rectangle. One approach would be to call layoutIfNeeded to force layout to happen immediately, then use view.frame.size.height. The init method is probably not an appropriate place to do this, as opposed to perhaps inside viewDidLoad on the controller. Or you might do this in viewDidLayoutSubviews to recalculate the corner radius every time your view's layout is updated (in which case you won't need layoutIfNeeded).
Don't use a height constraint if you already use top, bottom, leading, and trailing.
Try to visually debug your view by adding a background color, or using the debug view hierarchy button.
To get the height of a view you should use view.frame.size.height
EDIT (OP question edited)
Your problem is that you try to access the object height when it's just initialized but not displayed yet. You should create a function that update your UI and call it after your view is loaded.

UIScrollView how do you constrain a sub view that acts a container to all the other views?

so as you will see below I have a scrollview and I want to add it the the UIViewControllers root view. When I have it constrained to the top, right, bottom, and left I expect to see the red color take up the whole screen. This obviously works, but I want to add a subview to the scrollview that will wrap all the child views. How would I go about doing that?
I have added the view and I have set the same constraints except this time they are set from the wrapper view to the bounds of the UIScrollView, and the blue background color doesn't show anywhere. Also feel free to point out if this is a bad idea, but I thought I could just have it be constrained to the bottom and it will automatically extend the scrollviews content size as needed. This seems to work when I had all the subviews in the scrollview without a wrapper and the last view would extend the content size.
scrollView = UIScrollView(frame: view.bounds)
scrollView?.showsVerticalScrollIndicator = true
scrollView?.backgroundColor = .red
scrollView?.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView!)
scrollView?.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView?.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
scrollView?.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
scrollView?.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
//setup wrapper view
let subviewWrapper = UIView()
subviewWrapper.translatesAutoresizingMaskIntoConstraints = false
scrollView?.addSubview(subviewWrapper)
subviewWrapper.backgroundColor = .blue
subviewWrapper.topAnchor.constraint(equalTo: (scrollView?.topAnchor)!).isActive = true
subviewWrapper.leftAnchor.constraint(equalTo: (scrollView?.leftAnchor)!).isActive = true
subviewWrapper.rightAnchor.constraint(equalTo: (scrollView?.rightAnchor)!).isActive = true
subviewWrapper.bottomAnchor.constraint(equalTo: (scrollView?.bottomAnchor)!).isActive = true
Actually this is a very good idea. I always set up my scrollViews this way. I usually call the view contentView, but it is the same idea.
You're almost there. You haven't yet given Auto Layout anything to go on to figure out the size of your subviewWrapper. The constraints you've set so far pin the subviewWrapper to the edges of the scrollView's content area, but this just establishes the fact that as the subviewWrapper grows, the content size of the scrollView will expand. Currently your subviewWrapper has 0 width and 0 height which is why you see no blue.
Below are 3 examples of how you might establish the size of your subviewWrapper.
Note: Each of the following examples is completely independent. Look at each one separately and as you try them, remember to delete the constraints added by the previous example.
Example 1: Make subviewWrapper 1000 x 1000:
Set constraints to make your subviewWrapper 1000 x 1000 and you will see the blue and it will scroll in both directions.
subviewWrapper.widthAnchor.constraint(equalToConstant: 1000).isActive = true
subviewWrapper.heightAnchor.constraint(equalToConstant: 1000).isActive = true
Example 2: Vertical only scrolling with content size 2X of scrollView height:
If you set the width of your subviewWrapper to be equal to the width of the scrollView then it will only scroll vertically. If you set the height of subviewWrapper to 2X the height of scrollView, then your blue area will be twice the height of the scrollView.
subviewWrapper.widthAnchor.constraint(equalTo: scrollView!.widthAnchor, multiplier: 1.0).isActive = true
subviewWrapper.heightAnchor.constraint(equalTo: scrollView!.heightAnchor, multiplier: 2.0).isActive = true
Example 3: Size of subviewWrapper set by its subviews:
You can also establish the size of your subviewWrapper by adding subviews to it that are fully specified in size and connected in a chain from the top of subviewWrapper to the bottom, and from side to side. If you do this, Auto Layout will have enough information to compute the size of your subviewWrapper
In this example, I've added a yellow 600 x 600 square to the subviewWrapper and set it 100 points from each edge. Without having explicitly set a size for subviewWrapper, Auto Layout can figure out that it is 800 x 800.
let yellowSquare = UIView()
yellowSquare.translatesAutoresizingMaskIntoConstraints = false
yellowSquare.backgroundColor = .yellow
subviewWrapper.addSubview(yellowSquare)
yellowSquare.widthAnchor.constraint(equalToConstant: 600).isActive = true
yellowSquare.heightAnchor.constraint(equalToConstant: 600).isActive = true
yellowSquare.topAnchor.constraint(equalTo: subviewWrapper.topAnchor, constant: 100).isActive = true
yellowSquare.leadingAnchor.constraint(equalTo: subviewWrapper.leadingAnchor, constant: 100).isActive = true
yellowSquare.trailingAnchor.constraint(equalTo: subviewWrapper.trailingAnchor, constant: -100).isActive = true
yellowSquare.bottomAnchor.constraint(equalTo: subviewWrapper.bottomAnchor, constant: -100).isActive = true

How to center vertically labels in a view

I have 4 labels like this in a view:
The view hierarchy like this:
But if one of text in each label is empty, all of other labels should center vertically with the image.
For example: the albumDataLabel.text is empty, then userNameLabel, albumNameLabel, albumLocationLabel should center vertically with the image.
Somethings like this:
So how to do this, please point me to some approaches.
Set height constraint for every label and which label have not text
make it's height zero(from outlet of height constraint by setting constant to 0) at runtime.
Your constraint should be in linear hierarchy like first label's top should be pinned with it's supper view's top and last label's bottom should be pinned with superview's bottom and each and every label's bottom should be pinned with top of below label.
then you should set height constraint for view that contains all labels with constant (>=) of minimum height(least height of your view).
and centered vertically that view with your image view.
you can do this kind of setup!!
Since your 4 Labels are already in a view, you can set the labels' constraints to pin the first Label to the top, last Label the bottom and spacing in between to zero
Then select the view(withLabels) and your ImageView to align their vertical centers
Do not set a height value constraint for your labels nor the view
When one of your labels have an empty string, the height is automatically set to zero and hence 'hidden' so the view(withLabels) will shrink in height. All can be done in the interface builder, no coding necessary, it is just a matter of autolayout.
1) for your userNameLabel:
userNameLabel.leftAnchor.constraintEqualToAnchor(imageView.rightAnchor, constant: 10).active = true
userNameLabel.topAnchor.constraintEqualToAnchor(self.topAnchor, constant: 50).active = true
userNameLabel.widthAnchor.constraintEqualToConstant(220).active = true
userNameLabel.heightAnchor.constraintEqualToConstant(30).active = true
2) for your albumNameLabel:
albumNameLabel.widthAnchor.constraintEqualToConstant(220).active = true
albumNameLabel.heightAnchor.constraintEqualToConstant(30).active = true
albumNameLabel.topAnchor.constraintEqualToAnchor(userNameLabel.bottomAnchor, constant: 5).active = true
albumNameLabel.leftAnchor.constraintEqualToAnchor(imageView.leftAnchor, constant: 10).active = true
3) remember this:
self.addSubview(userNameLabel)
self.addSubview(albumNameLabel)
And go on in this way to all elements in your View.

Resources