Reputation: 1176
I have a view controller in which I would like to hide certain subviews, labels, etc., depending on what the trait collection is. For example, I have labels on the side I want to hide when compact horizontally, and labels on the top and bottom I want to hide when compact vertically.
Because I want to hide whole sections of the interface, it sounds like a UIStackView would make this easier. Likewise, I'm trying to do as much as I can within Interface Builder / Xcode (9.1 9B55) rather than in code, to try and keep it simple.
The main element of the design is a chess board, which:
Then labels and other items around it I want to move and hide based on the current trait collection.
I have begun by building a horizontal stack view, alignment, and distribution set to "Fill." Within it are two items, the boardView (purple) and labelView (yellow).
The labelView has a couple of labels in it, with constraints set within the view itself for the layout of these labels.
The boardView has a constraint of aspect ratio 1:1 (@1000):
I set the following constraints for the Stack View, pinning it to the superview (except for the bottom):
I set these constraints to insure the boardView never overflows the superview:
And then I set these constraints at a lower priority so that, if possible, the width or height will expand to fill the superview (but not overflow, since these are a lower priority than above):
This all seems to work great. No errors within Xcode and the app behaves as I would expect, in portrait and landscape (these screenshots from iPhone 8 simulator):
The problem comes when I try to hide the right view. I'm trying to do this by hiding the nameView when we are in regular mode vertically. I select nameView, click the + next to the Installed checkbox, select hR:
And then uncheck the Installed box for hR:
This looks great within Xcode. No errors and IB seems to be showing all the views as I would expect in both landscape and portrait (where the name labels are hidden):
So far, so good. In fact, when I run it, it looks just as we would expect, initially. If we start the simulator in the landscape, it looks good:
If we start the simulator in portrait, it looks good:
So what's the problem? Well, as soon as we rotate from portrait to landscape, the layout is completely wrong:
At the same time, we get in console a LayoutConstraints error:
2017-12-03 19:25:55.710381-0600 TestLayoutSIngleView[31439:3231004] [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 fixes it.
(
"<NSLayoutConstraint:0x60400009b8a0 UIView:0x7fe920603e90.width == UIView:0x7fe920603e90.height (active)>",
"<NSLayoutConstraint:0x60000009ced0 UIStackView:0x7fe92050bd70.leading == UILayoutGuide:0x6000001b5d20'UIViewSafeAreaLayoutGuide'.leading (active)>",
"<NSLayoutConstraint:0x60000009d100 UIStackView:0x7fe92050bd70.top == UILayoutGuide:0x6000001b5d20'UIViewSafeAreaLayoutGuide'.top (active)>",
"<NSLayoutConstraint:0x60000009d150 UILayoutGuide:0x6000001b5d20'UIViewSafeAreaLayoutGuide'.trailing == UIStackView:0x7fe92050bd70.trailing (active)>",
"<NSLayoutConstraint:0x60000009d1a0 UILayoutGuide:0x6000001b5d20'UIViewSafeAreaLayoutGuide'.bottom >= UIStackView:0x7fe92050bd70.bottom (active)>",
"<NSLayoutConstraint:0x60000009e5a0 'UISV-canvas-connection' UIStackView:0x7fe92050bd70.leading == UIView:0x7fe920603e90.leading (active)>",
"<NSLayoutConstraint:0x60000009ce30 'UISV-canvas-connection' UIStackView:0x7fe92050bd70.top == UIView:0x7fe920603e90.top (active)>",
"<NSLayoutConstraint:0x60000009e5f0 'UISV-canvas-connection' V:[UIView:0x7fe920603e90]-(0)-| (active, names: '|':UIStackView:0x7fe92050bd70 )>",
"<NSLayoutConstraint:0x60000009cde0 'UISV-canvas-connection' H:[UIView:0x7fe920603e90]-(0)-| (active, names: '|':UIStackView:0x7fe92050bd70 )>",
"<NSLayoutConstraint:0x60000009e7d0 'UIView-Encapsulated-Layout-Height' UIView:0x7fe92050b8f0.height == 375 (active)>",
"<NSLayoutConstraint:0x60000009e780 'UIView-Encapsulated-Layout-Width' UIView:0x7fe92050b8f0.width == 667 (active)>",
"<NSLayoutConstraint:0x60000009d060 'UIViewSafeAreaLayoutGuide-bottom' V:[UILayoutGuide:0x6000001b5d20'UIViewSafeAreaLayoutGuide']-(0)-| (active, names: '|':UIView:0x7fe92050b8f0 )>",
"<NSLayoutConstraint:0x60000009d010 'UIViewSafeAreaLayoutGuide-left' H:|-(0)-[UILayoutGuide:0x6000001b5d20'UIViewSafeAreaLayoutGuide'](LTR) (active, names: '|':UIView:0x7fe92050b8f0 )>",
"<NSLayoutConstraint:0x60000009d0b0 'UIViewSafeAreaLayoutGuide-right' H:[UILayoutGuide:0x6000001b5d20'UIViewSafeAreaLayoutGuide']-(0)-|(LTR) (active, names: '|':UIView:0x7fe92050b8f0 )>",
"<NSLayoutConstraint:0x60000009cfc0 'UIViewSafeAreaLayoutGuide-top' V:|-(0)-[UILayoutGuide:0x6000001b5d20'UIViewSafeAreaLayoutGuide'] (active, names: '|':UIView:0x7fe92050b8f0 )>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x60400009b8a0 UIView:0x7fe920603e90.width == UIView:0x7fe920603e90.height (active)>
I like the idea of using UIStackView to make Auto Layout and hiding views easier. But if what I see in Xcode doesn't work when compiled and run, it seems like this is going to be tricky.
What am I doing wrong (or am I running into a Xcode bug)?
I created a GitHub project which demonstrates the above: https://github.com/johnstewart/TestLayoutSIngleView
Edit.... It occurs to me as I've finished writing all of the above, that if we were to hide this view in landscape mode, the layout wouldn't be satisfiable... but I'm not hiding this view in landscape, only portrait. For some reason, iOS is making the change to the layout BEFORE the rotation.
If I set the priority of this from 1000 to 750:
... I don't get any auto layout errors on the console. However, the landscape view once rotated doesn't at all have things in the right place.
It's as if the stack view immediately is shrunk on beginning the rotation from portrait to landscape, and leaving the stack view in that shrunken state upon completion of the rotation, even though there is now plenty of room for it:
I sincerely hope I'm missing something obvious here... else I don't see how un-installing views based on trait collections with UIStackView is at all feasible; this seems like a straightforward example (and it is; I've got a lot more stuff to add!)
Edit:
The WWDC video where I saw this technique is from WWDC2017, Auto Layout Techniques in Interface Builder (https://developer.apple.com/videos/play/wwdc2017/412/). The demo for this begins at 29:00.
I was incorrect, however, that it was the "Installed" property that was being used for this. As the accepted answer indicates, "Hidden" was the way to do it.
Upvotes: 4
Views: 1804
Reputation: 964
I think you can achieve what you want without 'installed' property:
just set 'hidden' for portrait mode:
Upvotes: 5