33

From time to time I have a subview that I would like to remove from a layout. Not only should it be hidden, but it should not be considered part of the view's 'flow', so to speak. An example:

enter image description here

I am looking for a strategy to hide the orange view programmatically. The layout of the boxes, and their content, is via autolayout. Two things to note:

  • the orange box is defining its vertical height based on the content, plus some top/bottom offsets for margins. So, setting the labels' text to nil will only 'shrink' the view down to it's internal margins, it won't have a height of 0.
  • Similarly, the vertical spacing between the three boxes mean that even if the orange box's height is 0, the gap between red and yellow will be twice as large as required.

A possible solution

My best suggestion is to add a constraint to the orange box, setting it's height to 0. For this to work, I need to use non-required priorities for all of the vertical constraints inside the orange box. At the same time, the container should update the constant for the constraint that separates the boxes. I don't like this approach so much since the orange box class is defining it's internal constraints with it's superview's behavior in mind. Perhaps I could live with it if the orange box view instead exposes a 'collapse' method that adds the 0 height constraint itself.

Is there a better approach?

Ben Packard
  • 26,102
  • 25
  • 102
  • 183
  • Another option - remove the orange view completely and add new constraints to the superview for the vertical layout. Not sure if this is cleaner. – Ben Packard Feb 03 '15 at 14:55
  • Great question. In your example, is the Orange view laid out on the storyboard this way? Is there any way to prevent the situation in the first place? I've dealt with something similar where I made each of the views its own xib and added them to a container view in code. I also added the spacers in code as views between these xibs. If I knew a xib was not needed, I didn't add it or its spacer to the container view. – Mike Taverne Feb 03 '15 at 15:03
  • No storyboard, but in essence the same thing - the boxes are a separate UIView subclass – Ben Packard Feb 03 '15 at 21:06
  • This would very if you use stackviews. See [how hiding a subview in stackview will just remove it from the entire hierarchy](https://developer.apple.com/videos/play/wwdc2015/218/?time=708). – mfaani Dec 08 '17 at 17:08

4 Answers4

73

You can do this by adding an extra constraint between the yellow and red views of a lower priority, and adjusting the priorities in code.

enter image description here

The short dashed constraint (orangeToRedCon is the outlet) has a priority of 999 (you can't change a required priority to a non-required, so that's why it's not 1000). The long dashed constraint (yellowToRedCon) has a priority of 500 and a constant of 20. In code, you can hide the orange view, and swap those priority levels, and that will cause the yellow view to move up to whatever value you've set for the constant value of yellowToRedCon.

-(void)changePriorities {
    self.yellowToRedCon.priority = 999;
    self.orangeToRedCon.priority = 500;
    [UIView animateWithDuration:.5 animations:^{
        self.orangeView.alpha = 0;
        [self.view layoutIfNeeded];
    }];
}

This method doesn't require any changes in the orange view's height.

rdelmar
  • 103,982
  • 12
  • 207
  • 218
  • 3
    Best solution so far since I don't even have to update the orange view's labels' text. The whole view gets hidden and ignored from the layout, which is semantically exactly the problem I posed. – Ben Packard Feb 03 '15 at 21:10
  • I wonder if you would recommend switching the priorities for some reason over uninstalling/re-installing the vertical constraint? – Ben Packard Feb 03 '15 at 21:24
  • 1
    @BenPackard, I think it's easier, and you can animate the change if you want (I don't know if you can do that with adding and removing). – rdelmar Feb 03 '15 at 21:51
  • 7
    Careful, or you'll get this error: `'Mutating a priority from required to not on an installed constraint (or vice-versa) is not supported. You passed priority 1000 and the existing priority was 250.'` Just can't go above 1000 on these. – SimplGy Jun 10 '15 at 11:31
  • 3
    As long as you're dealing with priorities < 1000, this is the best solution. – Travis M. Apr 29 '16 at 16:52
  • I got this errror : "Mutating a priority from required to not on an installed constraint (or vice-versa) is not supported." – Quang Huynh Sep 27 '17 at 13:23
17

In iOS 9 you can use UIStackView for this. There also are polyfills for older versions: TZStackView and OAStackView

fabb
  • 11,660
  • 13
  • 67
  • 111
1

What you could do is have the height constraint of the orange view as an outlet (to be able to access it). then animate the collapse like so:

[UIView animateWithDuration:0.3 animations:^{
    orangeHeightConstraint.constant = 0;
    [self.view layoutIfNeeded]
}];

The orange view will have to have a top constraint to the red view and a bottom constraint to the yellow view. Also make sure to check Clip Subviews in IB or [orangeView clipsToBounds] programatically

Jad Feitrouni
  • 637
  • 6
  • 12
0

I would solve this by including all "necessary" spaces of a subview as part of the subview itself. This way, 1. Red View Height = visible red part + bottom space 2. Orange View Height = visible orange part + bottom space 3. Yellow View Height = visible yellow + bottom space

When you set the Orange View Height to 0 by Autolayout, it will automatically shrink the bottom space to 0 as well.

Vinod Vishwanath
  • 5,821
  • 2
  • 26
  • 40