20px gap on top of landscape view in iPhone app running on iPad - iphone

I am running an "iPhone-only" app in the iPad simulator...When the orientation of the device is changed to landscape mode, I have a view controller that kicks in and programmatically loads a WebView. This works swimmingly in the iPhone (no gap on top of landscape view), but when simulating in the iPad, there's a 20px (I think?) gap at the top of the view.
Here's the code in the landscape view controller's viewDidLoad where I load the WebView:
[super viewDidLoad];
// Initialize webview and add as a subview to LandscapeController's view
CGRect webFrame = [[UIScreen mainScreen] bounds]; // Use bounds to take up entire screen
self.myWebView = [[[UIWebView alloc] initWithFrame:webFrame] autorelease];
self.myWebView.scalesPageToFit = YES;
self.myWebView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
self.myWebView.delegate = self;
[self.view addSubview: self.myWebView];
// remove status bar from top of screen
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleBlackOpaque];
Setting the status bar to default, has no influence.
I can't seem to figure out why this would be fine on the iPhone, but emerge on the iPad???
Any ideas? Thanks in advance!

The screen bounds are in screen coordinates. You're adding the webview as a subview of self.view; its frame is in self.view coordinates. You want to fill your view, not the screen (your view is automatically resized by UIViewController/the rest of UIKit, which should end up resizing the web view to to auto-resizing):
self.myWebView = [[[UIWebView alloc] initWithFrame:self.view.bounds] autorelease];
It's not safe to change the status bar in -viewDidLoad. View-loading can happen anywhere (it happens when anything calls viewController.view). I'm also not sure why you're setting the style; you want to set hidden-ness:
In -viewWillAppear:, do [application setStatusBarHidden:YES animated:animated];
In -viewWillDisappear:, do [application setStatusBarHidden:NO animated:animated];
Finally, you might be seeing different behaviour because the iPad is running OS 3.2.x and the phone is running 3.1.x or 4.x. Additionally, the iPhone-compatibility mode uses a dummy status bar; the "real" status bar always stays at the edges of the screen.

20 pixels at the top is almost always invariably related to the height of the status bar.
The code you posted looks fine... for setting up the UIWebView. But you're adding it as a subview to your view controller's view. How is that sized? What is its frame?

I finally figured this out. The key missing component, which I didn't mention in the original post, is that the view controller that manages landscape is actually implemented as a modal view. (See the View Controller User Guide for code on how to do this) In concept, I have a Portrait view controller. (which is the primary controller) In the Portrait view controller's viewDidLoad I apply for a Notifier that is triggered off of a change in the orientation like so:
- (void)viewDidLoad {
[super viewDidLoad];
// SECTION to setup automatic alternate landscape view on rotation
// Uses a delegate to bring the landscape view controller up as a modal view controller
isShowingLandscapeView = NO;
// Create Landscape Controller programmatically
self.landscapeViewController = [[LandscapeViewController alloc] initWithNibName:#"LandscapeViewController" bundle:[NSBundle mainBundle]];
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(orientationChanged:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
// END SECTION landscape modal view controller
Then, when orientation changes this method is called:
- (void)orientationChanged:(NSNotification *)notification
{
UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
if (UIDeviceOrientationIsLandscape(deviceOrientation) && !isShowingLandscapeView)
{
// Load Landscape view
landscapeViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:self.landscapeViewController animated:YES];
isShowingLandscapeView = YES;
}
At the same time I was removing the status bar from the Landscape view controller's viewWillAppear method:
- (void)viewWillAppear:(BOOL)animated
{
// remove status bar from top of screen
[[UIApplication sharedApplication] setStatusBarHidden:YES animated:animated];
self.myWebView.delegate = self; // setup the delegate as the web view is shown
}
and this is where the problem is introduced. The Portrait view controller captures the screen dimensions, before transitioning to landscape as a Modal View. Then, viewWillAppear, in the Landscape view controller, removes the status bar.
So, the solution is to move the
[[UIApplication sharedApplication] setStatusBarHidden:YES animated:animated];
statement to the orientationChanged method in the Portrait view controller, BEFORE transitioning to the Landscape Modal View.
- (void)orientationChanged:(NSNotification *)notification
{
UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
if (UIDeviceOrientationIsLandscape(deviceOrientation) && !isShowingLandscapeView)
{
// remove status bar from top of screen
// NOTE: this must be declared BEFORE presenting the Modal View!!!! If it's not, the landscape view will
// contain an ugly white bar in place of the missing status bar at the top of the view.
[[UIApplication sharedApplication] setStatusBarHidden:YES];
// Load Landscape view
landscapeViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:self.landscapeViewController animated:YES];
isShowingLandscapeView = YES;
}
Note, that as tc helpfully mentioned above, if you want the status bar to appear when orienting back to Portrait, then you need
[[UIApplication sharedApplication] setStatusBarHidden:NO animated:animated];
in the viewWillDisappear method in the Landscape view controller.

Related

On user action rotate UIViewController

What i have:
i have a viewcontroller that supports Portrait and landscape. When in landscape i show a filter view for the content of the portrait.
What i need to do:
When the user selects a filter in the landscape view i want to force rotate the device back to portrait.
I have done some Googling and discovered
[[UIDevice currentDevice] setOrientation:UIInterfaceOrientationPortrait];
This is a private API and i dont want to do that. (But excatly what i am after)
In short - on user action i want to force rotate the device.
Is there something i am missing from the docs?
Any ideas would be super.
Dan
Learned this from experience! Do this in your viewDidLoad::
UIViewController *vc = [[UIViewController alloc] init];
[self presentModalViewControllerAnimated:NO];
[self dismissModalViewControllerAnimated:NO];
It will force landscape, provided you return YES for shouldAutorotateToInterfaceOrientation for only landscape. No private APIs!
You can try with this If works.
-(void)forceFullyPortraitView{
[self.appDel.navigationController.view removeFromSuperview];
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait];
[ [UIApplication sharedApplication].self.delegate.window addSubview:self.appDel.navigationController.view];
self.navigationController.navigationBar.hidden=NO;
}
-(void)forceFullyLandscapeView{
[self.appDel.navigationController.view removeFromSuperview];
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeRight];
[ [UIApplication sharedApplication].self.delegate.window addSubview:self.appDel.navigationController.view];
self.navigationController.navigationBar.hidden=NO;
}
You can set landScepe right or left.

IOS: How to put some view on top of presented modal view controller?

I have an activity view that I have added in AppDelegate class to tap bar:
[self.mainTabBar.view addSubview: spinner];
When there are connection problems it is visible in each view controller and is spinning.
There is some button at certain view controller, makes to present some modal view controller.
That modal view controller overlaps the spinner. How to make that spinner always be on top of all views or at least on top of that modal view controller?
I tried to make such a thing in view controller that presents modal view controller:
[self presentModalViewController:selectionViewController animated:YES];
[self.view bringSubviewToFront:[self.tabBarController.view viewWithTag:15]];
Not works.
Add the view to the main window.
UIWindow* mainWindow = [[UIApplication sharedApplication] keyWindow];
[mainWindow addSubview: spinner];
While phix23's answer is correct, here is a more complete example:
//The view you want to present
UIViewController *viewControllerYouWantToPresentOnTop = [[UIViewController alloc] initWithNibName:nil bundle:nil];
//Create transparent host view for presenting the above view
UIWindow* mainWindow = [[UIApplication sharedApplication] keyWindow];
UIViewController *viewControllerForPresentation = [[UIViewController alloc] init];
[[viewControllerForPresentation view] setBackgroundColor:[UIColor clearColor]];
[[viewControllerForPresentation view] setOpaque:FALSE];
[mainWindow addSubview:[viewControllerForPresentation view]];
//Make your transparent view controller present your actual view controller
[viewControllerForPresentation presentViewController:viewControllerYouWantToPresentOnTop animated:TRUE];
Remember to clean up after yourself when you don't need these any longer.
This code can be used from anywhere in your app, even a library :)
An app normally displays its content within a single window throughout its life.
But there are situations where an extra window may be used to add content on top of everything else. Apple ensures UIAlertView always stays on top by adding it in a separate window.
UIView *contentView = [[UIView alloc] initWithFrame:contentFrame];
contentView.backgroundColor = [UIColor greenColor];
UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectMake(x,y,contentFrame.size.width, contentFrame.size.height)];
window.windowLevel = UIWindowLevelAlert;
[window addSubview:contentView];
[window makeKeyAndVisible];
Show and hide your window by setting window.hidden = Yes or No as needed.
This will always show your contentView on top of everything else in the app.
The modal controller is in a completely different layer, you cannot make any subview of the presenting controller to overlap it.
Use a UIAlertView with a spinner inside. The alerts are displayed in a layer which overlaps even modal controllers.
Place the view to the keyWindow, as suggested above. You might also need to set Presentation style of the modal view as Current Context, otherwise, it can still pop on top

Iphone: Cannot switch back from my landscape view to a portrait view

I am working on an app (my first one), which is basically a TabBar app.
To be more precise there are:
- a login view controller
- a tab bar controller (when login is done)
- a landscape view controller that is used when the first itel of the TabBar is switch from Portrait to Landscape.
So, when I am in the first tab, I need to be able to move to landscape view to display some other data. In my tab bar controller, I have implemented those methods:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
if([self selectedIndex] == 0)
return YES;
return NO;
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
// Get AppDelegate
MyAppDelegate *delegate = [[UIApplication sharedApplication] delegate];
if (toInterfaceOrientation == UIInterfaceOrientationLandscapeRight)
{
// Remove TabBarView and add graph Landscape View
[self.view removeFromSuperview];
[delegate setSubViewLandscapeViewController];
}
}
In the delegate, I have implemented the setSubViewLandscapeViewController and the setSubViewTabBarController:
- (void)setSubViewTabBarViewController {
[window addSubview:[tabBarController view]];
}
- (void)setSubViewGraphLandscapeViewController {
[window addSubview:[landscapeViewController view]];
}
I want the landscapeViewController to display only in landscape mode, I have then (in my landscapeViewController):
- (void)viewWillAppear:(BOOL)animated {
[[UIDevice currentDevice] setOrientation:UIInterfaceOrientationLandscapeRight];
}
// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationLandscapeRight);
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
NSLog(#"willRotateToInterfaceOrientation");
}
A part of this works fine, I mean the switch from portrait to landscape is ok (when I am in the first tab), the tabbarcontroller is remove from the SuperView and the landscape view is added as a subview instead.
The thing is... I do not know how to switch back to portrait mode (and then load the previous controller, the tabBar one using the setSubViewTabBarViewController of my delegate). It seems none of the willRotateToOrientation, willRotateFromOrientation, .. are triggered when I actually move the device from the landscape view...
In short, when I am in the landscape view I do not know what to do to move back to the tabbar view... I am kind of stuck in the landscape view once I am in this one.
Thanks a lot for your help,
Luc
Look at the pie chart in CPTestApp-iPhone in the examples folder. It handles rotation by implementing -(void)didRotateFromInterfaceOrientation: and resizing the graph after a rotation.
Well, I managed to get a solution for this problem.
In fact, while moving from portrait to landscape I removed the tabbarcontroller from window subview and add the landscapeviewcontroller instead.
It seems it was not the correct thing to do.
Instead, I add the landscapeViewController as subview of the tabbarcontroller and remove it when going from landscape to portrait.
I still have a problem however with the y position of the landscape view which seems to changes when I do several decive rotation in a row....
Regards,
Luc

How does presentModalViewController interact with nested UITabBarController and UINavigationController

I have a view that I want to take up the full screen, so I override the init method, and some of the view methods:
- (id) init {
if (self = [super init]) {
self.wantsFullScreenLayout = YES;
}
return self;
}
- (void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[[UIApplication sharedApplication] setStatusBarHidden:YES animated:YES];
}
- (void) viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[[UIApplication sharedApplication] setStatusBarHidden:NO animated:YES];
}
Now, from another screen, I want to display it as a modal view:
UIViewController *screen = [[MyScreen alloc] init];
[self presentModalViewController:screen];
[screen release];
All pretty standard stuff. When I want the full-screen view to go away, however, the previous view is shifted or stretched up by about 40 pixels.
Specificially, I have a UITabBarController with a UINavigationController inside, displaying a UITableViewController, which is the view that displays the subview, and also the view that gets shifted up. If the table is not in a navigation controller, everything works just fine, nothing gets shifted up at all. If I experiment with commenting out the wantsFullScreenLayout and setStatusBarHidden lines with no navigation bar, it sometimes shifts up just 20 pixels, or doesn't actually display on the full screen (but later it does without changing any code), or sometimes doesn't break at all (but I am not getting the full full screen with any of these)
What am I doing wrong?
Through some combination of Sean's suggestion and jumping up the responder chain, I've found a solution that works is what seems like all circumstances (so far).
First issue:
The Table View by itself does not display in a navigation controller, but may show up in one if being selected from the more view in the tab bar, and that's the case where displaying the modal view in full screen causes the table to underlap the navigation bar upon return.
Second issue:
When not displayed in a navigation controller, presenting the modal view does not take up the full screen (even though wantsFullScreenLayout is set to YES). When returning from this view, the view is shifted up by 20 pixels and you can see a gap between the bottom of the table and the top of the tab bar.
Solution:
- (void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.navigationController setNavigationBarHidden:YES animated:NO];
[self.navigationController setNavigationBarHidden:NO animated:NO];
}
- (void) presentModalViewController:(UIViewController *)screen animated:(BOOL)animated {
UIResponder *responder = self;
while (responder && ![responder isKindOfClass:[UITabBarController class]]) {
responder = [responder nextResponder];
}
[(UIViewController *)responder presentModalViewController:screen animated:YES];
}
The toggling of the navigation bar's visibility forces the relayout. Overriding presentModalViewController actually calls presentModalViewController on the tab bar controller instead, which then causes it to show in the full screen. For some reason, self.tabBarController is nil when not in the more view controller, so I had to jump up the responder chain to find it.
Your UINavigationController will get called with the viewWillAppear before the modal view is dismissed. Have you tried calling [[UIApplication sharedApplication] setStatusBarHidden:NO animated:NO]; inside the controllers that can be visible post modal dismissal. I have run into tons of problems displaying modal views on top of UINavigationControllers when bounds change. It fights any layout changes and requires lots of resetting to previous states to get it behaving nicely. It might also not hurt to call [self.navigationController setNavigationBarHidden:NO animated:NO] as well to force layout.
If this works well it might serve you to create a simple baseclass that sets these in it's viewWillAppear and then just subclass it for all non modal view controllers.
If this doesn't work you might try placing a swap view at the top level that contains the tab bar controller and then you could remove the tab bar controller with a transition when you present your modal view. Yes this isn't technically modal but would still look nice and offer the same effect. At that time since the view controller is out of the view hierarchy it shouldn't get it's layout all munged.
I think this has to do with the timing of the presentModalViewController: call. As a test you could try adding sleep(3) before you call that method. If that fixes anything, or even if it doesn't i guess I would try moving the order of things around. maybe viewDidDisappear and viewDidAppear instead of 'Will'

Switching back from horizontal view causes iPhone application to crash

Here is my problem:
- I have one navcontroller and inside it one tabbar controller with 4 view controller.
- I want to add following functionality:
- On landscapeRight to dismiss navcontroller, tabbar controller and everything and load whole new controller - this one goes ok, here is the code:
- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
// Just delete the lines for the orientations you don't want to support
if(toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft) {
[appDelegate ToHorizontalAverageResponseView:self];
}
return YES;
}
Here is the code in the App Delegate:
- (void)ToHorizontalAverageResponseView:(id)sender
{
HorizontalResponseViewController *tempController = [[HorizontalResponseViewController alloc] initWithNibName:nil bundle:nil];
[self setHorizontalResponseViewController: tempController];
[tempController release];
//[UIApplication sharedApplication].statusBarOrientation = UIInterfaceOrientationLandscapeRight;
// View rotation transformation
CGAffineTransform landscapeTransform = CGAffineTransformMakeRotation( M_PI * 90.0 / 180.0 );
[[horizontalResponseViewController view] setTransform:landscapeTransform];
[window addSubview:[horizontalResponseViewController view]];
}
The question is how to transfer back to portrait view and all those navbar and tabbar controllers?
Thx,
Mladen
It sounds like you are trying to build something along the lines of the iPod-CoverFlow transition. The question (on how to build the coverflow effect) was asked in Stanford's CS193P lecture and the answer hinted in the following direction:
If you can afford the memory overhead, it may be the best thing to add a new landscape view on top of all the portait views (not discarding them as you suggest). The user then interacts with this landscape view as desired until going back to portrait. All this time, the portrait views remained quietly in the background.