How come some of my UIViews are shifted after navigation?
Asked Answered
L

5

10

In some of my application designs or for just some UIViews, following a navigationController's pushViewController, my new view will be shifted off the window by the height of the status bar. As a result, I will put this code stub in the viewDidLoad method.

CGRect  frameAt = [self.view frame];
CGRect  statusBarFrame = [[UIApplication sharedApplication] statusBarFrame];
frameAt.origin.y += statusBarFrame.size.height;
[self.view setFrame: frameAt];

It does not make sense to me that this is the intention of XCode and Interface Builder, so I suspect that I am doing something fundamentally wrong with the SDK during my view design. Furthermore, on the rare occasion that I don't have to shift my view, I really don't know what the difference in the two design approaches.

Note also, that most of the time I try to design my views using IB, with some minor customization.

Does anyone else run into this and know what they do to fix without such a code stub?

Landseer answered 27/1, 2010 at 4:6 Comment(1)
Also note, I have played with the 'simulated status' setting and at least once, that appeared make the correct adjustment so I didn't have to make the change in the code.Landseer
B
16

I've used Apple's NavBar sample code to try and reproduce this problem.

the applicationDidFinishLaunching is originally implemented like this:

[window addSubview:navigationController.view];
[window makeKeyAndVisible];

If I change it to this:

UIViewController *shellController = [[UIViewController alloc] initWithNibName:nil bundle:nil];
[shellController.view addSubview:navigationController.view];
[window addSubview:shellController.view];
[window makeKeyAndVisible];

Then I get the gap appearing.

However if I only do this:

UIView *shell = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[shell addSubview:navigationController.view];
[window addSubview:shell];
[window makeKeyAndVisible];

Then everything looks normal.

So I guess every view controller will offset its view if its the root view controller. But the navigation controller overrides this behaviour to offset its view regardless of whether it's the root view controller. Therefore I would say your gap is appearing because you've got a navigation controller somewhere lower in the hierarchy than it's supposed to be.

Balinese answered 5/3, 2010 at 6:1 Comment(3)
I think you are on to the problem. I am going to check out my other app that has two of its screens with this problem. I think I may have added a UINavigationController to those UIViewControllers. Back in about 20 minutes ...Landseer
Mystery solved! That is it!!! I really appreciate your diligence on digging into this. I have learned a really great principle for adding a navigation bar to existing UIViewControllers. The way I had them, they were defined "lower in the UIView hierarchy"; such that when I added them as IBOutlet in the code, instantiated in IB and hooked the IB's nav controller to the IBOutlet from my code, I got both the navigation feature I wanted and no viewing artifacts. Besides, my setting the frame caused a disturbing 'jump' to snap back in place. This is 110% the correct answer to my problem.Landseer
I noticed this was answered in March, 10. Not sure if this is compatible with iOS6. I'm having a similar problem, but the gap is below the navigation bar. Here is my question: #17791446 Thanks.Bough
B
2

The main thing to keep in mind here is that a view controller will set the frame of its view itself. This is because the amount of space available to the view may change during the lifetime of the app, and only the view controller knows how to adjust the view's frame appropriately. Examples of when the amount of space changes include the navigation bar changing height, turning the device from portrait to landscape and the status bar can also increase in height if the user is taking a call. Because of this you should not modify the view's frame yourself.

So, the first thing you need to is is remove all code related to modifiying the view's frame.

Now you need to design your views with the mindset that the frame size could change at any moment. This means setting the autoresizing property of each subview properly. If you do this, then it won't matter if you turn on the simulated navigation and status bars or not; they're just there to help you see what the final result will look like in most cases.

You can set the autoresizing property of each subview in Interface Builder in the Size Inspector (the one with the ruler icon). In the animation, the white box represents the root view of the view controller, the red box represents the currently selected subview. You'll notice that the subview is anchored to the top-left corner of the root view by default. This is fine if the size of the view never changes, but we know that not to be true. If you have subviews that you want to appear at the bottom no-matter-what, then you need to play with the diagram to the left. The way it works is if one of the four lines around the edge is selected, then the distance between that edge of the root view and the edge of the subview is fixed. So if you want a subview to appear at the bottom, you need to make sure the bottom-most line is selected and not the top. The two lines in the middle affect whether the size of the subview changes when the root view change size. So, for example, if you had a table view that you wanted to occupy the entire height of the screen, you would make sure the inner vertical line was selected. This is called the struts and springs model.

If you are adding subviews programatically you need to set the autoresizingMask property on each subview. Here's an explanation.

Hope that helps!

Balinese answered 4/3, 2010 at 8:35 Comment(10)
Hi Moshy, I guess you confirmed what I believed (let the system manage the views and frames). Unfortunately, over time, something in my coding will tweak something and the frames shift. It is usually the view of a subclassed view controller when I navigate away and then return. I guess my understanding is (was) correct and my real question should be -- how do I determine what I did to tweak my hierarchy and which fundamental parameter / setting do I have to fix. I have also read (multiple times) the referenced doc. I will do a little more review before I decide this is the answer.Landseer
Moshy, I read over Brian's question and responders. Several of them found that they have to set the frame to itself's size -- which is what I have also done. This sounds like some race condition of size during orientation detection or something fundamental. I will check out my subviews' autosizing and if that works, I will award the answer to you.Landseer
Hmm, it might help if you post your view controller structure. The docs specify: When deploying a navigation interface, you must install this view as the root of whatever view hierarchy you are creating. For example, if you are deploying the navigation interface by itself, you would make this view the main subview of your window. To install a navigation interface inside a tab bar interface, you would install the navigation controller’s view as the root view of the appropriate tab.Balinese
Ok .. in one of my major applications, I check 100% all views and views that were added with addSubviews and they were all set with autosizing. I commented some esoteric views. With all that, the problem persisted. What I did find is that I had allocated a UINavigationController (not yet 'deployed', so it is not hooked up to anything) and if I commented it out, the problem went away -- my screen did not shift. For this reason, I can't say your suggested answered my problem (the nav controller was not visible).Landseer
Neither did Brian's suggestions with regard to using the UITabController to manage pushing the view. I then worked with IB and I was able to let IB create the Navigation Controller - set as an IBOutlet in my program, now I have an allocated nav controller and the views are all correct. I am going to leave this unanswered unless you can give me a plausible reason for the UINavigationController.Landseer
BTW - I should point out that I did not 'fix' the problem for this app with setting the frame. I have other Applications that I will apply your principle to and remove those setFrame's.Landseer
Moshy, thanks for the quick response. I will check out my nav controller usage and see if I can tie it back to your doc assessment. I really appreciate your careful breakdown for me. 23 hours to go before the bounty expires ... you are close :)Landseer
Is there a good way to post the structure since it is rather involved and mostly in IB.Landseer
Something simple like: UIWindow > UINavigationController > {UIViewController, UIViewController} What I'm trying to find out is if the navigation controller's view is the root view of the window, because I think it just assumes it is even when it's not. Then maybe it thinks it has to offset the view to accommodate the status bar, when in actual fact its parent view controller has already done that!Balinese
I'm going to add another answer, because I think the solution is actually unrelated to this one.Balinese
E
1

I've run into similar issues. Check out my two previous questions:

IPhone - After dismissing Modal View Controller - gap is left at top of page

IPhone - UIView addSubview Gap at top

Eggcup answered 5/3, 2010 at 1:55 Comment(2)
Hi Brian, As I mentioned in my comments to Moshy (above), I did review your Q&A (which I think I had researched before asking my question) and they appeared to be promising, but they did not address my problem. Besides, I think the setting the frame to itself's current setting is the wrong solution as something else is more likely wrong. I am going to leave the unanswered as yet (I have answered the problem for one of my apps, but I don't think it is the solution for the other apps).Landseer
Good stuff -- these responses were helpful for my R&D of the problem and offered further insight into the SDK, however, Moshy's answer directly fixed my problem and had some clean-up side effects. You might want to revisit your old problem and see if this is a better solutions. - Sincerely Thanks for your effort.Landseer
N
0

link text

A similar bug is discussed here.

Also is animation set to NO? Try setting it to YES as this solved a similar problem I was facing.

Neil answered 29/6, 2010 at 7:14 Comment(0)
O
0

Moshy's answer was very helpful as I finally realized the meaning of the dotted/solid lines in IB for controlling the resize properties of UIView elements.

However, adjusting those properties did not address a similar problem I faced with one of my views. This view had a status and top bar defined in IB. It was a slightly heavy one, containing a UIWebView that would load a HTML string within viewWillAppear and a few other interface elements.

While loading the view, if the user suddenly changed the orientation of the device from portrait to landscape, all contents of the view would shift downwards by the height of the status bar. The resulting gap between the view controls and its top would remain even after switching back to portrait orientation.

What finally solved my issues, and my remaining hair, was adding the line:

    self.view.frame = [[UIScreen mainScreen] bounds];

within

-(void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration

Ever since my view contents are held in place despite abrupt device orientation changes.

Organogenesis answered 1/2, 2012 at 17:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.