0

I'm using the following layout as a custom popup UIView in Xcode 13 (the white background is transparent):

enter image description here

When the screen orientation is changed to landscape mode, the constraint at the top and bottom are still 100pts. Because of that the middle part (yellow, UIView with UIStackView with UITableView,... inside) is really small and a warning shows up in console about the top (red) and bottom (blue) bar:

Unable to simultaneously satisfy constraints.

I know what this warning means. To fix it I created the following function...

private let constraintPortrait:CGFloat = 100
private let constraintLandscape:CGFloat = 10

private func fixConstraints() {
    if (UIDevice.current.orientation == .landscapeLeft || UIDevice.current.orientation == .landscapeRight) && UIDevice.current.userInterfaceIdiom == .phone {
        topConstraint.constant = constraintLandscape
        bottomConstraint.constant = constraintLandscape
    } else {
        topConstraint.constant = constraintPortrait
        bottomConstraint.constant = constraintPortrait
    }
}

... and call it both in viewDidLoad and viewDidLayoutSubviews. This was working great but every now and then the warning still popped up, so I added prints to viewDidLoad,... and noticed that the warning is actually printed before my constraint fix is called. I renamend viewDidLayoutSubviews to viewWillLayoutSubviews (UIViewController lifecycle here) and Abracadabra!, the warning was gone.

People usually recommend to use viewDidLayoutSubviews when you want to do stuff after the device was rotated but hardly ever mention viewWillLayoutSubviews and while searching for a reason for that I found this answer, saying not to use the latter to change constraints because it might cause another autolayout pass.

Question:

What should I use instead to prevent the conflicts (without changing the fixed constraints for portrait mode!)? Is there a way to change the top and bottom constraint automatically and solely in the Interface Builder, without using any code and only when actually necessary (-> always keep the 100pts in portrait mode, even with a long table, but switch to 10pts instantly in landscape mode when there isn't enough space)?

20
  • Are you certain why the errors are occurring? With the constraints that you have shown in the question I wouldn't expect a change in orientation to cause unsatisfiable constraints unless you have a fixed width/height constraint somewhere? (or something similar). Could you post the auto layout error into the question? Alternatively, this tool is really good for visualising the error wtfautolayout.com Commented Oct 14, 2021 at 9:54
  • Hmm... just looked again... Are you trying to centre the "pop up" view in the middle of the parent view? If so there is a less constraining way of doing this. Commented Oct 14, 2021 at 9:56
  • @Fogmeister I checked the warning again and the red and blue UIView (50pts) are definitely mentioned first. Afterwards it complains about the 100pts and then there's something about a fixed height of 320pts, which I'm not sure about where it's coming from (have to check again). I can't change the height of the red or blue view because I want the whole thing to look like a regular UIAlertController, so the next best thing is changing the 100pts constraints (which works but might not be the proper way to do it). Commented Oct 14, 2021 at 10:17
  • And yes, I want to center the popup in the middle of the parent view but also don't want it to get too big, that's why I'm using constraints (which work for both small and big devices) instead of a fixed height that could be too small for big devices and too big for small ones. Commented Oct 14, 2021 at 10:19
  • I think it would maybe make sense here to approach this the other way around and set a maximum height/width on the pop up view. So that it's size is defined internally, not externally. And then have it centered in the parent view. If you want to set a minimum spacing to the parent view edges (your 100point constraints) then still have them but set them as >= 100 rather that ==100. That would mean you wouldn't have to change them at all for different orientations. Commented Oct 14, 2021 at 10:58

1 Answer 1

0

viewWillLayoutSubviews is correct. Any layout changes you perform here, including changes of constraints, will be animated automatically in coordination with the rotation animation.

But how will you know that this call to viewWillLayoutSubviews is due to rotation? Implement this method:

https://developer.apple.com/documentation/uikit/uicontentcontainer/1621466-viewwilltransition

Or, on an iPhone, this method:

https://developer.apple.com/documentation/uikit/uicontentcontainer/1621511-willtransition

I like the former because it works on both iPad and iPhone. These are called before viewWillLayoutSubviews, so you can set an instance property to signal to yourself that the size is officially changing. You can work out what's happening by comparing the bounds size height to the bounds size width, and change the constraints accordingly.

Sign up to request clarification or add additional context in comments.

7 Comments

Does willTransition also trigger for iPads? I just added it with a print and it seems to work fine but it's called even before viewWillLayoutSubviews is, so won't that have the same problem with re-draws? What's actually the difference between those two? I found willTransition multiple times while googling but people seem to use it to animate their UIView (e.g. make it roll in from the right), which I don't need or even want to do because that would draw additional attention to the yellow view not being big enough. I'm also not sure what to do with the collection or coordinator.
Sorry, I oversimplified by accident. Fixed now.
Thanks for the edit. The viewWillTransition doc says that it triggers whenever the VC's UIView's size is changed, how do I know that that function triggered because of a rotation? I always check what the current rotation is (see code above), so I could simply remember the previous one, compare and act accordingly. If I did it that way, is there any reason to use willTransition over viewWillLayoutSubviews (or the other way around)?
I don't understand what you're asking. A zillion things can trigger layout. Only something very dramatic such as rotation can trigger viewWillTransition.
I did some more testing with it and you're right, willTransition does trigger less often than viewWillLayoutSubviews and I've only noticed the print after changing rotation so far. Would you recommand comparing the rotation to the previous one with willTransition, just to be absolutely sure?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.