How to programmatically create UIView with landscape orientation? - iphone

I have an iPad application with fixed landscape orientation. Everything works OK until the moment when I programmatically create UIView and add it to my root UIViewController's view - it's orientation is always set to portrait while all other content is displayed in landscape mode. This UIView is a modal view showing some custom form for user.
AlertView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, [[UIScreen mainScreen] bounds].size.width, [[UIScreen mainScreen] bounds].size.height)];
AlertView.opaque = NO;
AlertView.backgroundColor = [UIColor blackColor];
AlertView.alpha = 0.7;
<...> // some other configuration code
[window addSubview:AlertView];
So the question is: how can I initiate UIView with landscape orientation? All I can think of is using transform like:
modalView.transform = CGAffineTransformMakeRotation( ( 180 * M_PI ) / 360 );
Though it's not too elegant way...

The reason your AlertView gets displayed in portrait orientation while the rest of your app gets displayed in landscape is because you are adding the AlertView as a subview of your window rather than adding it as a subview of a view controller's view. This places it outside of the view hierarchy that gets transformed automatically through autorotation.
The app's window object coordinates autorotation by finding its topmost subview that has a corresponding view controller. Upon device rotation, the window calls shouldAutorotateToInterfaceOrientation: on this view controller and transforms its view as appropriate. If you want your view to autorotate, it must be a part of this view's hierarchy.
So instead of [window addSubview:AlertView];, do something like [self.view addSubview:AlertView];.

You should try setting an autoresizing mask.
When the ViewController rotates, your AlertView's bounds would change, too.
yourView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);

To make UIViews in landscape orientation, and provided you want your root view controller to always be in portrait mode, you have 2 options:
Do it the way you are doing it by rotating the UIView using a transform
Change the way you are doing it to allow
your root view controller to rotate (easiest). The view controller will then handle the rotations of your UIViews for you

Related

Rotate according to topmost/last subview?

I have a UITabController that is displayed by becoming a subview of the window (and then animated to slide in):
[self.window addSubview:tabController.view];
CGRect endFrame = tabController.view.frame;
CGRect startFrame = tabController.view.frame;
startFrame.origin.y += [[UIScreen mainScreen] bounds].size.height;
tabController.view.frame = startFrame;
[UIView animateWithDuration:.5 animations:^{
tabController.view.frame = endFrame;
}];
My problem is that I want this subview to not rotate, at all, and be portrait only. Despite shouldAutorotateToInterfaceOrientation and shouldAutoRotate returning false in my UITabController's subview, parts of it still rotate - like the status bar, and the UINavigationBar changes size.
Is there a way to fix this, or simply a better way of doing things? Maybe a way to force the statusbar rotation and UINavigationBar height?
Define the following in your UITabController:
- (UIInterfaceOrientation) supportedInterfaceOrientations
{ return UIInterfaceOrientationPortrain; }
From iOS documentation:
Declaring a Preferred Presentation Orientation
When a view controller
is presented full-screen to show its content, sometimes the content
appears best when viewed in a particular orientation in mind. If the
content can only be displayed in that orientation, then you simply
return that as the only orientation from your
supportedInterfaceOrientations method.

Layout Multiview Rotated iPhone App with a Toolbar

I have a multiview app with a Toolbar. The Root view controller controls the toolbar, but the other views have their own view controller class. I use code like this to load the views:
-(IBAction)loadBand1Start:(id)sender{
[self clearView];
Band1Start *band1Controller = [[Band1Start alloc] initWithNibName:#"Band1Start" bundle:nil];
self.band1Start = band1Controller;
[band1Controller release];
[self.view insertSubview:band1Start.view atIndex:0];
if (UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)) {
band1Start.view.frame=CGRectMake(0, 0, 480.0, 260.0);
} else {
band1Start.view.frame=CGRectMake(0, 0, 320.0, 400.0);
}
}
The IF statement is to make it load in the correct orientation. Without this, although it rotates ok, it always loads in Portrait, even if the phone is in landscape.
Autosize and Auto Rotate works. I used IB to build my nibs in portrait. When the phone is rotated to landscape, they do rotate and resize, however with some nibs, the rotated layout does not look right. There is overlaps etc, which is a common problem, and I know can be fixed using CGRectMake.
However, it does not matter where I put the CGRectCode, it does not execute. I have tried putting it in willRotateToInterfaceOrientation, willAnimateRotationToInterfaceOrientation, and viewWillAppear, but the rotated layout is not changed.
If I use the code below to switch views, the rotated layout is how I want it, but the toolbar does not appear:
-(IBAction)loadBand1Start:(id)sender{
[self clearView];
Band1Start *band1Controller = [[Band1Start alloc] initWithNibName:#"Band1Start" bundle:nil];
[self presentModalViewController:band1Controller animated:NO];
[band1Controller release];
}
I think this is because the view controller for the toolbar needs to be the root view controller for the toolbar to appear, but only the root view controller receives the orientation change notifications.
So if I keep my toolbar controller as the root view controller, the subview's controller does not receive the orientation notifications, so does not execute my CGRectMake code, but if I make the subview's controller the root view controller, it receives the notifications ok, but the toolbar does not appear because it's controller is not the root controller.
Am I correct about the problem here? Is there a way around it?

Setting the correct UIView frame size on alloc/init depending on device orientation

I have set up all my views in portrait mode, then set the autoresize mask accordingly so that it will auto resize the views as the orientation changes.... however, this seems to work OK for views that are already displayed. If I load any new views whilst in landscape mode I get inverted width and height values on the loaded view's frame.
Do I have to calculate the frame size based on orientation for each of the main views?
e.g., instead of
UIView* view = [[UIView alloc] initWithFrame:CGRectMake(0,0,500, 100)];
I would have to do:
CGRect frame;
if([UIDevice orientation] == UIDeviceOrientationLandscape){
frame = CGRectMake(0,0,100,500)
}else{
frame = CGRectMake(0,0,500,100)
}
UIView* view = [[UIView alloc] initWithFrame:frame];
It seems a bit long-winded to me, given that the autorotation once a view is loaded works perfectly!
How do you manage view frames?
thanks
I manage my view frames using [[UIScreen mainScreen] applicationFrame];.
It is also very useful when building universal apps as you can use it to design views that will display correctly on both devices.

iOS -- how do you control the size of a modal view controller?

I am presenting a modal view controller. If it matters, it is scrolling up from the bottom. How can I control what portion of the screen it occupies?
EDIT: I have the following in the modal view controller. It's not helping.
- (void)viewDidLoad {
TestResultView *trv = [[TestResultView alloc]initWithTest: [Model m].currentTest];
self.view = trv;
trv.frame = CGRectMake(0, 320, 320, 160);
[trv release];
[super viewDidLoad];
}
You can modify the frame of the view controller, but if you're using UIViewController's -presentModalViewController:animated: method, the view behind will be unloaded once your modal view is finished animating onto the screen (This assumes you're on an iPhone) and you'll see a white screen where your background view should be. iOS assumes that your modal view controller will be a full-screen view controller, and dumps the other view to save memory.
If you really want to show a view over part of the screen, you should instead add the UIView (no UIViewController) to your current UIViewController's view as a subview, and then animate it onscreen yourself. I think something like this would work in your UIViewController class that will present the view:
// Add the view as a subview and position it offscreen just below the current view
UIView *myHalfView = [[UIView alloc] initWithFrame:someAppropriateFrame];
[self.view addSubview:myHalfView];
CGRect offScreenFrame = myHalfView.bounds;
offScreenFrame.origin = CGPointMake(0.0, CGRectGetMaxY(self.view.frame));
// Now animate the view upwards
[UIView beginAnimations:nil context:nil];
// Move the view upwards the height of your sliding view so it's entirely onscreen
myHalfView.center = CGPointMake(myHalfView.center.x, myHalfView.center.y - myHalfView.bounds.size.height);
[UIView commitAnimations];
[myHalfView release];
For bonus points, you could fade the view in by setting
myHalfView.alpha = 0.0;
before the UIView animation block, and setting
myHalfView.alpha = 1.0;
inside the block after animating the center property.
When you're done, you can do something similar but in reverse to slide the view offscreen. You can add an animationDidStop selector to the UIView animation block to be notified when the view has slid off screen so that you can remove it from the view hierarchy.
From an aesthetic point of view, you should also be careful how you do this since having a view slide up is a standard behavior, and if your view looks like a normal view but stops halfway, users may feel (even briefly) that the app has frozen. They'll figure it out, but it will leave a bad feeling about your app if not handled carefully. Mainly, I would avoid using standard full-screen cues like including a UINavigationController at the top of your view to help users understand what's going on. Half-sheets tend to be UIActionSheets on the iPhone, so think in that direction.
That is nice, the above accepted answer explains a nice hack to present subViews which feel like ModalViews, but what if it is an iPad, and i can indeed give it a modalViewController which doesnt cover the entire screen.
In case of iPads, I dont think the underneath view will be unloaded. ( because there are options where we can present the modalView on iPads, which dont cover the entire screen )
ModalViewController in the end is a controller itself, and like any other controller has a root view, whose properties can be editted, if we can get hold of it.
Here is what will give you a custom frame of the ModalView :
MyViewController *viewController = [[MyViewController alloc] init];
viewConroller.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentModalViewController:viewController animated:YES];
//superView of viewController's view is modalViewController's view, which we were after
viewController.view.superview.frame = CGRectMake(x,y,w,h);
//x y w h - can have desired values.
I would add to #dsaw's answer that the superview of the modal view does not seem to rotate its coordinate system in landscape mode. Here is the code that I used in my own app:
MyViewController* modalVC = [[MyViewController alloc] init];
modalVC.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentModalViewController:modalVC animated:NO];
CGRect r = CGRectMake(self.view.bounds.size.width/2 - 236,
self.view.bounds.size.height/2 - 130,
472, 260);
r = [self.view convertRect:r toView:modalVC.view.superview.superview];
modalVC.view.superview.frame = r;
While the superview may not rotate itself with the iPad, it does seem to do the right thing and keep the modal view centered if I rotate the iPad after showing the modal view.

UIScreen applicationFrame returning incorrect rectangle on landscape application launch (iPhone/iPad)

Having trouble getting the correct bounds for my iPad application when launching it in landscape mode. I have the proper keys set in my Info.plist file, and my view controllers launch properly in landscape (and portrait, natch).
In my applicationDidFinishLaunching: method I'm calling a selector after a 3 second delay, and that method makes a call to [[UIScreen mainScreen] applicationFrame], but it's returning me a portrait frame (ie height > width).
Does anyone know how to fix this? It smells like a bug to me (if so I'll file a radar), but if it's intended behaviour, where is it documented?
I never rely on [[UIScreen mainScreen] applicationFrame], especially during app launch.
When creating views in code, use the superview to set your frame.
If you're using xibs with "simulated interface elements" they will be correctly sized and everything will work great.
UINavigationController based apps
In the case of a UINavigationController based app, grab the frame directly from self.navigationController.view, don't try to use [self loadView] and self.view.superview. UINavigationController uses "hidden" subviews to do it's job--so the direct superview will not work.
UINavigationController is special because during app launch, the navigation controller resizes your views after loadView gets called. When autoresizing kicks in you end up with a small margin at the bottom of the screen.
Why not UIScreen
[[UIScreen mainScreen] applicationFrame] doesn't work reliably (especially during app launch in landscape). My experience is that the viewcontroller's interfaceOrientation property will not match the applicationFrame orientation.
CGRect bounds = [[UIScreen mainScreen] bounds]; // portrait bounds
if (UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation])) {
bounds.size = CGSizeMake(bounds.size.height, bounds.size.width);
}
When you are holding the iPad in landscape orientation and launch an app, the view controller initially sees bounds for a portrait view (even though orientation reports landscape). The view controller will then get a message to rotate to landscape orientation before it appears.
This is the way I get the correct CGRect when the view controller is on landscape:
CGRect landscapeBounds;
if (self.view.frame.size.width < self.view.frame.size.height)
landscapeBounds = CGRectMake(self.view.frame.origin.y, self.view.frame.origin.x, self.view.frame.size.height, self.view.frame.size.width);
else
landscapeBounds = CGRectMake(self.view.frame.origin.x, self.view.frame.origin.y, self.view.frame.size.width, self.view.frame.size.height);
This is as designed. You should query the size of your superview and adjust as necessary.
[[UIScreen mainScreen] applicationFrame] will always return the portrait rectangle even if the app is in landscape mode. This is related to the fact that UIWindow never actually rotates, but just changes the transform of rootViewController.view instead.
To make sure, you can print the root view object in portrait and landscape modes, and you'll see something like this:
Portrait:
<UIView: 0x96290e0; frame = (0 20; 768 1004); ...
Landscape:
<UIView: 0x96290e0; frame = (0 20; 768 1004); transform = [0, 1, -1, 0, 0, 0]; ...
So, add a launch image and give it the suffix -568h, according to Apple's guides.
I don't understand why anyone with a sound mind would make a system setting depend on a graphic; but I just tested and it worked.
Here is the spot that taught me after a quick search I didn't see this answer above, figured it'd be useful to someone.
S
I got into same problem when dismissing view with dismissViewControllerAnimated in Cordova plugin.
I modified singingAtom code for viewWillAppear method in MainViewController, which got resized after dismissing modal view:
- (void)viewWillAppear:(BOOL)animated
{
CGRect appFrame = [[UIScreen mainScreen] applicationFrame];
UIDeviceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
if (UIDeviceOrientationLandscapeLeft == orientation ||
UIDeviceOrientationLandscapeRight == orientation)
{
if (appFrame.size.width < appFrame.size.height)
{
appFrame = CGRectMake(appFrame.origin.y, appFrame.origin.x, appFrame.size.height, appFrame.size.width);
}
}
self.view.frame = appFrame;
[super viewWillAppear:animated];
}