Unable to programatically remove views

Originator:cameron
Number:rdar://23105634 Date Originated:14/10/2015
Status:Open Resolved:No
Product:IB Product Version:7.0
Classification:Bug Reproducible:Always
 
Summary:
When using IB to create your interface if you use size classes and have one of more views in the view hierarchy that are uninstalled for a specific size class you are no longer able to add/remove views to the view hierarchy at runtime especially in viewDidLoad.

If you don't uninstall any views in IB the views will be added/removed as expected. The issue only occurs when any other view is uninstalled for a specific size class, then all other views can't be removed programatically in viewDidLoad.

It's worth making clear that I'm not talking about uninstalling view A in IB and then trying to remove it in code, I'm talking about trying to remove view B programmatically, while uninstalling view A in IB (for a specific size class), there are completely separate views that are in the same view hierarchy. 

I've reached out to Apple Developer Relations and have been told the following by Justin Miller:

"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.".

While this explains the issue it appears to me this is a bug or design flaw. As far as I can tell this is documented nowhere on apple.com. It's always been possible to modify the view hierarchy programatically and there is no mention in the documentation for addSubview or removeFromSuperview that this might pointless if you're using size classes and the install/uninstall IB option.

Steps to Reproduce:
1) Install Xcode.
2) Create a project from the Single View Application template.
3) Edit the associated storyboard and add two views (i.e. labels) to the Any/Any size class.
4) Setup some constraints for the two views.
5) Switch to the Regular/Regular size class.
6) Select one of the views and uninstall it for this specific size class.
7) Now switch back to the Any/Any size class.
8) Create an outlet connection from the other view (not the one just uninstalled) to the view controller.
9) From the Project Navigator select the associated view controller .m file.
10) In the viewDidLoad method add code to remove the outlet view from the superview i.e. [self.myView removeFromSuperview].
11) Run the project and observe that the view to be removed programatically is still visible.

Expected Results:
The view that received the removeFromSuperview call should be removed at runtime.

Actual Results:
The view stays visible in the view hierarchy.

Version:
Xcode 7.0
iOS 9
OS X 10.11.1

Notes:


Configuration:
Happens in Xcode 7.0 running iOS 9. I've not tried in earlier views but expect the result to be the same for iOS 8 and Xcode 6.

Comments

Reply to Apple.

I really would urge you guys to reconsider this a bug or at least a design flaw. No where is it documented that this is the behaviour, it might be an internal implementation detail but the ability to add/remove views at run-time is hardly an edge-case. You mention that I should just not use size classes but you've deprecated virtually every other way of building targeted interfaces i.e. deprecating all the view controller rotation callback methods, UIUserInterfaceIdiom etc.

Apple Developer Relations

Engineering has provided the following information regarding this issue:

Please know that that if you need to express more complicated logic, then you cannot use the checkboxes in IB.

If you don't want your view hierarchy to be controlled by the trait environment, please don't use the size class trait checkboxes in IB.

Whenever there are two ways to do something, there's likely to be a combination of those ways that is accepted by the compiler but doesn't do what you want. This is one of those cases.

Initial response from Apple before filing bug report view Apple Developer Support

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.

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.

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.


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!