Mastering Auto Layout - Part 7: Challenge: | Ray Wenderlich Videos

Use your Auto Layout coding skills along with your knowledge of Stack Views in order to simplify your layout code.


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/4260-mastering-auto-layout/lessons/7

I thinks this whole bunch of courses are somewhat for intermediate rather than beginners…

I mean, the videos are good, but not enough for understanding later courses!

Where can I get the fundamentals used on these beginner tutorials?
(Like, how could I know what method should I use at specific circumstance? Should I read API documentation of swift, which is pretty hard for beginners to understand ?)

@kidneypill Please check out our Auto Layout course for beginners when you get a chance:

https://www.raywenderlich.com/7478-beginning-auto-layout

I hope it helps!

Of course I already checked it out.
Is there any way to fill the gap between the two lectures?

@kidneypill You could start with the Swift Apprentice to learn Swift:

https://store.raywenderlich.com/products/swift-apprentice

and then move on to the iOS Apprentice to learn how to make iOS apps:

https://store.raywenderlich.com/products/ios-apprentice

Please let me know if you have any other questions or issues about the whole thing.

Thank you!

1 Like

Hi @jessycatterwaul, I have a question about the constraints. In the code version, containerGuide.trailingAnchor was set to be lessThanOrEqualTo the safeAreaLayoutGuide.trailingAnchor, but in this video, stackView’s trailingAnchor was set to greaterThanOrEqualTo the safeArea. Why was it not lessThanOrEqualTo like in the code version? Thanks.

Hi @landtanin!

This just has to do with the order of the items in the constraints. If you choose Reverse First and Second Item, you can use a <= instead.

41%20PM

And to match the code exactly, I should have changed from the superview to the safe area. Thanks for helping me catch that!

Thanks @jessycatterwaul. I’m glad the question helped you found something too!

I found the explanation for the challenge to be less than clear. Please could you expand for future users?

1 Like

@jessycatterwaul @catie Can you please help with this when you get a chance? Thank you - much appreciated! :]

I’ve got a question relating to the call order of ViewDidLayoutSubviews() and ViewWillLayoutSubviews():
I added the stack view programatically and tried to position it at one third of the total height of the view with stackView.centerYAnchor.constraint(equalTo: view.topAnchor, constant: view.frame.height / 3).
I was aware that you could only perform calculations based on the views size attributes within the methods ViewDidLayoutSubviews() and ViewWillLayoutSubviews(), so I started out by adding the constraints to ViewDidLayoutSubviews() as advised by Catie.
When I ran the app, however, and rotated to landscape the first portrait screen would work fine, as would the landscape screen after rotation. But if I rotated back to portrait, the stack view would be stuck at the top of the screen instead of at one third of it. The debug console told me:

2019-11-14 11:13:56.401208+0100 PirateHandbook[28912:2386832] [LayoutConstraints] Unable to simultaneously satisfy constraints.

Probably at least one of the constraints in the following list is one you don't want. 

Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.

(
"<NSLayoutConstraint:0x60000328e440 UIStackView:0x7fb508a22220.centerY == UIView:0x7fb508a259b0.top + 298.667 (active)>",

"<NSLayoutConstraint:0x6000032f0730 UIStackView:0x7fb508a22220.centerY == UIView:0x7fb508a259b0.top + 138 (active)>"
)

Will attempt to recover by breaking constraint

<NSLayoutConstraint:0x60000328e440 UIStackView:0x7fb508a22220.centerY == UIView:0x7fb508a259b0.top + 298.667 (active)>

I figured that the “constant” part of the constraint really was a constant and was apparently not recalculated as the size classes change. So I tried to remove the constraints from both the view and the stack view in the willTransition(to:) method that is called on size class changes. Still, this had no effect. Only when I changed ViewDidLayoutSubviews() to ViewWillLayoutSubviews(), did the code finally work and the stack view got correctly positioned on every change.

This got me thoroughly confused about the role of those two methods, as the removal of the constraints happens in the willTransition(to:) method, which - to my understanding - is called way before the view actually begins laying out the subviews again. I would be grateful for some clarification.

The frame height in question will be the same in viewWillLayoutSubviews and viewDidLayoutSubviews (I think of those as “the methods between which Auto Layout happens”), so it’s not a matter of incorrect numbers. You just have a situation where the old frame height isn’t being thrown out of your constraints (at least by the time it needs to be).

As is often the case with Auto Layout, you can simplify your solution by using a multiplier, not a constant that ever needs to be recalculated. You have to use NSLayoutConstraints, foregoing the convenience of the anchor-based API, but it’s worth it.

Thanks for the prompt reply, that did the trick!
Following your suggestion, I attached the stack view’s centerX property to the view’s bottom property with a multiplier of 0.33. Is that a viable approach or is there a more “elegant” solution?

Regarding ViewWillLayoutSubviews() and ViewDidLayoutSubviews(), I do get that AutoLayout constraints are set up “inbetween” those two methods, but shouldn’t the constraints have been invalidated even before that within the body of willTransition(to:)? I mean, that method is called immediately before the rotation and subsequently the view and its subviews are laid out again using the layout methods, or am I missing something here?

Just change the 0.33 to 1 / 3 and that’s as clean as I can think of. :soap: (Not only is two decimal places already not precise enough for today’s screens, but the denser screens get, the more precision you’ll need, to avoid pixel imprecision. Plus, the fraction is more meaningful to a reader than a decimal approximation. Side note: SwiftUI is better for rounding to pixel boundaries than Auto Layout.)

Almost. The constraints are still valid until the view size actually changes (right before viewWillLayoutSubviews).

…which makes these equivalent, necessitating a code change if you switch where the constraint activation happens:
view.frame.height in view[Will/Did]LayoutSubviews
= size.height in viewWillTransition(to size:)

As for why you had an extra constraint that wasn’t being deactivated, I can’t say without looking at your code. So upload it if you need another set of eyes! :eyes:

Thanks a ton!
My implementation pretty much matched yours, but I declared the constraints array as optional as part of the property initializers to make it accessible throughout the entire scope of WeatherViewController. Then I called willTransition(to:):

override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
    super.willTransition(to: newCollection, with: coordinator)

    view.removeConstraints(constraints!)
    stackView.removeConstraints(constraints!)

}

As outlined in my previous comment, that worked only when I moved the constraints setup from ViewDidLayoutSubviews() to ViewWillLayoutSubviews(). But considering your explanation, that makes sense now.