Saturday, December 22, 2012

View gets inset by 20 points when instantiated through instantiateViewControllerWithIdentifier

In a recent project I had a custom container view controller in which I wanted to embed another custom content controller, as a child controller. Since the content controller already resided in the storyboard I thought I'd use a sequence of code like so:


    PdfViewController *pvc = [self.storyboard instantiateViewControllerWithIdentifier:@"PdfViewController"];
    [self addChildViewController:pvc];
    [self.view addSubview:pvc.view];
    [pvc didMoveToParentViewController:self];


Giving it a try produced this result:



You can see an unsightly white band at the top of the screen. By the looks of it it seems about the same size as the status bar. It's as if the view of the embedded view controller loaded by instantiateViewControllerWithIdentifier is inset to make room for the status bar. A little debugging confirmed that indeed that's the case:


(lldb) p (CGRect)[[pvc view] frame]
(CGRect) $1 = origin=(x=0, y=20) size=(width=320, height=460)
(lldb) 


My first thought was that it may be caused by the default "Status Bar" setting under the "Simulated Metrics" section in the utility pane. That "simulated" qualifier still raised some questions. AFAIK those simulated metrics are only used as an aid in designing the UI, without any effect at runtime. A more promising clue should be given by the size inspector of that view in IB:


Hmm, looks like that 20 points offset might be the culprit. Yet that's a grayed-out value. How to change that ? Let's fiddle with the simulated metrics, just for kicks:


and check out what we get as a result:


You'd think this would fix the problem. Alas it doesn't. I think this may be a bug in Xcode. I believe the  change in the simulated metrics shouldn't affect the value of the view's frame. In any case, I find the interplay between the "Simulated Metrics" of the view controller and the "Size Inspector" of its view pretty confusing. For example, sometime you can't change the struts and springs in "Size Inspector" unless you set the "Size" in  "Simulated Metrics" to freeform.

So it looks like we need to fix this in code, by pinning the frame of the embedded view in the top-left corner of its superview, like so:

    PdfViewController *pvc = [self.storyboard instantiateViewControllerWithIdentifier:@"PdfViewController"];
    [self addChildViewController:pvc];
    pvc.view.frame = CGRectMake(0, 0, pvc.view.frame.size.width, pvc.view.frame.size.height); // fix
    [self.view addSubview:pvc.view];
    [pvc didMoveToParentViewController:self];

This takes care of the problem. Hopefully Apple will keep improving their Interface Builder, and add more CASE capabilities to Xcode. I fondly remember using ObjecTime and enjoying the productivity gains that afforded. After all the best code is no code. Easiest to write, read and maintain :).


UPDATE:

I found a better fix for this issue. It started as a solution for another problem. It turns out that if you have  a view controller defined in the storyboard, with a non-standard size, when loaded by default would show up at full screen size. It turns out all we need is to deselect the "Resize View From NIB" checkbox. This also eliminates that 20 points inset at the top.