Autolayout blocks view from being removed from superview?

So there is a button I'd like to throw out of the view hiearchy. If I do:


-(void)viewDidLoad
{
[super viewDidLoad];
  [self.button removeFromSuperView];
}


When I run the app, I see the button.


Now if I do:


-(void)viewDidLoad
{
   [super viewDidLoad];
   self.button.hidden = YES;
}

It's hidden.


What's the proper way to deal with this? I'd image that the layout engine would automatically throw out the constraints of a view when it's removed from the view hierarchy.


Not sure what constraint would 'force' a subview to remain in it's superview. Align center in container maybe? I don't really want to make outlets to like 20 different constraints.

Well I figured it out. it's a bug in Interface Builder it seems.


If you add a subview for a particular size class....(say Width Regular and Height Regular)...that's all you need to do.


Switch back to Any x Any... try to remove a subview it from the superview...and it won't happen. Adding a subview for a specific size class throws it off. It'll still hide though.

I would add some screenshots..but it doesn't look like the new forums support attaching images?

Use 'hidden', I think. Test to confirm it doesn't disrupt the view hierarchy.

I mentioned that hidden doesn't disrupt the view hierarchy, but there is a particular reason why I'd rather remove the button than just hide it.


My screen stacks about 5 buttons vertically to make a main menu. On 3.5 inch iPhones, only 3 buttons will fit on screen. The other two buttons are extras that will be moved into the toolbar on 3.5 inch (making the font size for the buttons teeny to make it fit does not look good). The content shouldn't scroll either. If size classing behavior was more detailed, I'd just be able to do this in IB, but the groupings don't provide enough control.


Also, the app supports all orientations. On iPhone 5, a couple of the buttons will be hidden in landscape, and shown in portrait. I'd rather just remove the buttons in viewDidload on the small screen. On orientation change I don't want to have to do extra screen sniffing before showing buttons. Better to just get them out of there in viewDidLoad....so on 3.5 inch iPhones it'll just be sending messages to nil.


I can't imagine why a layout engine would block a view being removed from the hierachy? I should be able to toss it, no?

The method is "removeFromSuperview", not "removeFromSuperView". If you are experiencing problems with code, you need to post actual code, copied and pasted, that reproduces the problem.


What I'm getting at is I suspect there's some other issue going on. removeFromSuperview should always work if the object is non-nil and is in the view hierarchy.

Ah....not that simple, perhaps.


I think testing shows 'hidden' not to be an issue, tho?


As for screen sniffing, you shouldn't have to do that, I think.

Well....it certainly is 'possible' that something else is going on. I find it peculiar, because I usually don't use autolayout...and I'd expect that when i call removeFromSuperview, that it'd be gone. My typo in my previous post, has nothing to do with it. It's safe to say, that that typo wouldn't compile...so that would have nothing to do with it. The same 'button' is responding to 'hidden'....because I don't see it...when I add that line right beneath it.


I tried reproducing the issue in a small sample project...but in the new project the button is being removed as expected. There is somethiing with these constraints in this storyboard scene that must be causing this to happen.

Most of the time, I wouldn't screen sniff. They don't give you any indication that you're on the 3.5 inch screen when you use size classes...even though it has a different aspect ratio than the other iPhones. For many layouts...that's fine...especially on something that scrolls like a table view. But if you want to truly take advantage of a screen size and not compromise the layout on the shorter screen by making font sizes too small, there's no other way.


I'm starting to like how autolayout is working in Interface Builder. I wish they gave us more control.....a way for us to create are own size classes because there categories may not work for every layout. It would be nice if we could group are layouts exactly as we want to in Interface Builder.

Accepted Answer

Well I figured it out. it's a bug in Interface Builder it seems.


If you add a subview for a particular size class....(say Width Regular and Height Regular)...that's all you need to do.


Switch back to Any x Any... try to remove a subview it from the superview...and it won't happen. Adding a subview for a specific size class throws it off. It'll still hide though.

I would add some screenshots..but it doesn't look like the new forums support attaching images?

Yep, no way to add images here...yet, but thanks for the followup and good luck w/it.

Here's the bug number:


21477829

From the docs on size classes, that sounds like expected behaviour, if the view is not activated in the current size class at runtime. Its superview property will be nil and so removeFromSuperview will have no effect. But if you're also seeing the view on screen, and it's not supposed to apply to the runtime size class, then yes that does sound more like a bug.


Keep in mind that if you're doing stuff in viewDidLoad, that might be too early... I would guess like view sizes, the trait collection might still be whatever the default is from the storyboard (not sure about that but I would check it out). You might need to respond to the traitCollectionDidChange callback and do your hiding / removal there. I would think you should just be hiding the view anyway so you can adapt to screen size changes (i.e. unhide it later if the view becomes bigger). Right now I don't think there's a scenario where the height would change back from 480 to 568 or bigger, but Apple has been sending LOTS of smoke signals lately that we need to be able to display in any rect the OS gives us at runtime.

"From the docs on size classes, that sounds like expected behaviour, if the view is not activated in the current size class at runtime. Its superview property will be nil and so removeFromSuperview will have no effect. But if you're also seeing the view on screen, and it's not supposed to apply to the runtime size class, then yes that does sound more like a bug."


The view is activated in the current size class. To reproduce this problem all you have to do is add another different view that is only activated in another size class to cause the issue to happen. The issue only happens on storyboard scenes that have 'size class' specific views...but the size class specific view isn't the one i'm trying to manipulate.

I've spoken to Apple and here is the repsonse they gave me to me email:

As things are currently implemented, if size classes are enabled the system will reset the view hierarchy back to the state defined in the storyboard whenever the current trait collection changes. This process only affects a view's subviews and the installed constraints. Thus 'label' will be (re)added as a subview of ViewController.view but if you were to hide 'label' instead of removing it, then it would remain hidden.


> ...*when* the middle label has been uninstalled for a particular size class.


This is the result of internal optimization. If there are no differences between the [w:Regular], [w:Compact], [h:Regular], and [h:Compact] size classes (as configured in the storyboard) then the system avoids performing unnecessary work, leaving the view hierarchy and installed constraints unmodified.


> This feel like a bug to me but seems impossible that this could have slipped

> though the net so I'm hoping this is just an implementation detail and that

> the process for how and when to remove a view programmatically when using

> size classes has changed.


If you need to add and remove part of your view hierarchy programmatically, the right approach is to factor that part of your interface into a separate top-level view that you programmatically insert into the main view hierarchy at runtime. Interface Builder allows you to drag additional Views from the Object Library into the top level of the outline for your view controller's scene (See the attached screenshot). These additional views appear above your view controller's scene in the canvas. You can add subviews and connect IBOutlets as usual.


I've opened this is a bug as I feel it's a design flaw, I've posted this on Open Radar for your convenience:

https://openradar.appspot.com/23105634


If you agree this is a bug then please also file it as a bug at https://bugreport.apple.com/ as the more people that report it the more likely Apple will fix it.

Thanks.

Autolayout blocks view from being removed from superview?
 
 
Q