UIWindow orientation issues (two UIWindows and landscape mode) - ios

I know that this looks like a huge question but it's not so don' be afraid to read it. Mostly it's me explaining how stuff works.
I have two UIWindows in my app. The first one is the main window which gets created by the app by default. The second one is called modalWindow and is also created in the app delegate.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.modalWindow = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.modalWindow.windowLevel = UIWindowLevelStatusBar;
//Other stuff I need
//....
return YES;
}
Most of my app is portrait only but there is one view controller where the user can switch to landscape orientation. I'm listening for landscape change via:
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:nil];
This works fine and in the orientationChanged method I present the landscape view controller. All is well at this point. If I rotate back to portrait I get the orientationChanged fired once more at which point I dismiss the landscape view controller.
Now let's explain how the second window comes into play. When in landscape mode there is an action (just a button press) which presents a new view controller on the second window (the modalWindow). Ok let's explain this.
The app delegate has two methods which look like so:
- (void)presentActivityOnModalWindow:(UIViewController *)viewController
{
self.modalWindow.rootViewController = viewController;
self.modalWindow.hidden = NO;
}
- (void)modalTransitionFinished:(NSNotification *)notif
{
self.modalWindow.hidden = YES;
self.modalWindow.rootViewController = nil;
}
I call the presentActivityOnModalWindow via the [[[UIApplication sharedApplication] delegate] performSelector....]. I know it isn't the best practice but it works fine. As for the dismissal I use NSNotificationCenter to post the notification about dismissal. The view controller that gets presented on the modalWindow is supporting only portrait mode. It's returning YES in shouldAutorotate and UIInterfaceOrientationMaskPortrait in supportedInterfaceOrientations.
Ok a lot of explaining but now we get to the problem. When I rotate to landscape, pop up the modalWindow, dismiss the modal window and rotate back to portrait my main window is still in landscape mode and everything looks catastrophic.
I tried adding an observer to both window and modalWindow and logging changes in frame and bounds and here are the results:
Did finish launching:
window frame {{0, 0}, {320, 568}}
window bounds {{0, 0}, {320, 568}}
modalWindow frame {{0, 0}, {320, 568}}
modalWindow bounds {{0, 0}, {320, 568}}
Rotate to landscape:
window frame {{0, 0}, {568, 320}}
Open activity in landscape:
modalWindow frame {{0, 0}, {320, 568}}
modalWindow frame {{0, 0}, {320, 568}}
Dismiss activity:
none
Rotate back to portrait:
none
So as it seems my window does not get back to normal frame when we get back to portrait mode. How to fix this? If I need to provide any more details feel free to ask.

The system will only handle rotation of your keyWindow. If you have other windows you'll have to handle rotation yourself.
I also think that modal controllers is the way to go. But if you really want to handle rotations take a look at how other "custom windows" libraries handle rotation. Alert views are a great example:
https://github.com/search?o=desc&q=uialertview&s=stars&type=Repositories&utf8=✓

Apple does not encourage using windows in this manner. It is probably best to use an UIViewController displayed modally in stead of your
self.modalWindow = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
Presenting an UIViewController modally is done from an UIViewController instance using
[self presentViewController:<#(UIViewController *)#> animated:<#(BOOL)#> completion:<#^(void)completion#>]

You are creating 2 windows and Apple recommends against this. An app should really have as many windows as there are display devices.(SEE UIWindow reference )
I would suggest using 2 views and "drawing" everything onto those views according to the orientation. Then make one view hidden, when the other is displayed.
Alternatively you could just use 1 view and make adjustments at the time of orientation changes. The ease of doing this depends on the type of information being displayed.
Orientation changes are best detected by AppDelegate code.
Here is a sample of code I use to detect an orientation change and then a check for the new dimensions of the window.
- (void)application:(UIApplication *)application didChangeStatusBarOrientation:(UIInterfaceOrientation)oldStatusBarOrientation
{
//other stuff from rotate:
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
CGRect appFrame = [[UIScreen mainScreen ]applicationFrame];//using frame so status bar is not part of calculations.
if (orientation == UIInterfaceOrientationLandscapeRight
||
orientation == UIInterfaceOrientationLandscapeLeft
)
{
self.currentAppScreenWidth = appFrame.size.height;
self.currentAppScreenHeight = appFrame.size.width;
[YOURapp reArrangeSubViews_and_Layouts: appFrame];//Something like this - maybe.
}
else
{
self.currentAppScreenWidth = appFrame.size.width;
self.currentAppScreenHeight = appFrame.size.height;
[YOURapp reArrangeSubViews_and_Layouts: appFrame];//Something like this - maybe.
}
}
Hope that helps.
One other thing. If you are displaying the same info with a different layout, then you should be able to lay it out using Xcode tools, to adapt to orientation in a suitable way. If certain things are still not perfect, then try to tweak using code.
If you are displaying completely different information, the 2 view approach might be the easiest.
[Maybe some more information about the differences in the data being displayed on the 2 screens, if the above solution does not work for you.
Cheers.]

Related

UIDevice set orientation doesn't change view bounds

I call:
[[UIDevice currentDevice] setValue:[NSNumber numberWithInteger: UIInterfaceOrientationLandscapeLeft]
forKey:#"orientation"];
And then I log out:
NSLog(#" = %#",NSStringFromCGRect(self.view.bounds));
And I get:
2015-04-06 11:04:13.02 [1032:262526] = {{0, 0}, {375, 667}}
This is on an iPhone 6. How come the bounds didn't change?
EDIT
So I saw in another answer that [UIScreen mainScreen].bounds gives the proper dimensions after setting rotation programmatically using setValue:forKey.
However, how come the view still has the portrait frame? Is this a delayed response? Do I have to rotate the view myself?
If you want to change interface orientation then you should look towards this callbacks
interfaceOrientation property
– shouldAutorotateToInterfaceOrientation:
+ attemptRotationToDeviceOrientation
– rotatingHeaderView
– rotatingFooterView
– willRotateToInterfaceOrientation:duration:
– willAnimateRotationToInterfaceOrientation:duration:
– didRotateFromInterfaceOrientation:
and I will suggest you to look towards this method too.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceO‌​rientation
Please refer this docs
adject your code inside this
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceO‌​rientation
if UIInterfaceOrientationLandscapeLeft{//code} else if UIInterfaceOrientationLandscapeRight{//code}

how is Core-plot supporting portrait to landscape transitions

Have been looking at the latest version of Core-plot ce0fa44812 and the associated example code.
It supports device orientation, however I am unable to determine the mechanism used to provide this support. I see the orientation options provided in the plist.
In particular I am interested in the Real Time plot example, however see no calls to change the bounds upon rotation from portrait to landscape.
So what magic is going on here? There are the bounds being changed.
That version of the Plot Gallery example app uses a storyboard with the new iOS 8 split view controller to manage all layout and transitions. The views in each storyboard scene use auto layout to resize automatically when needed.
You can change the frame of hostingView when the device rotate.
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator: (id<UIViewControllerTransitionCoordinator>)coordinator {
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
} completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
[self.hostingView setFrame:[self.view bounds]];
}];
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
}
Then you will change to the landscape plot.
If you want to your device is held in portrait before you plot the graph.
You need to set the view controller. Otherwise, there is something like a bug, that the plot is set in portrait size in a landscape mode.
-(void)viewDidLayoutSubviews {
[self.hostingView setFrame:[self.view bounds]];
}

Objective-c device orientation

I am building a template app. I am having some issues with the device rotation/orientation. I have the following in my appDelegate didFinishLaunching:
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
and in my VC viewDidLoad:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(deviceOrientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil];
...
//add the title bar
titleBar = [[OAI_TitleBar alloc] initWithFrame:CGRectMake(0.0, 0.0, 1024.0, 50.0)];
titleBar.titleBarTitle = #"OAI Template";
titleBar.hasAccount = YES;
titleBar.hasReset = YES;
titleBar.hasHome = YES;
[titleBar buildTitleBar];
[self.view addSubview:titleBar];
This is my deviceOrientationDidChange method:
//rotate the vc view
if (UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)) {
[self.view setFrame:CGRectMake(0.0, 0.0, 1024.0, 768.0)];
} else if (UIDeviceOrientationIsPortrait([UIDevice currentDevice].orientation)) {
[self.view setFrame:CGRectMake(0.0, 0.0, 768.0, 1024.0)];
}
//call the rotation management methods of various classes
[titleBar adjustForRotation:orientation];
}
This works fine when I rotate the device, the views adjust as expected. But when the app launches, the x,y coordinates are wrong - when I log the frame when launching in portrait orientation, the VC view's frame is {{0, 20}, {768, 1004}}, when launching in landscape mode it is {{20, 0}, {748, 1024}}
The titlebar frame is {{20, 0}, {748, 1024}} for landscape and portrait (which is as I coded it).
However, what I am seeing in the simulator and on my device is drastically different. What you should see is a black bar (50 pixels or so in height) at the top with a logo on the left, followed by some buttons and then the app title aligned right. As you can see from the images below, when the app launches in either orientation it's opening x/y coords are no where near 0/20. Any help would be appreciated.
What it looks like to me is that when the app launches in landscape it is being displayed as portrait and when it launches as portrait, though the display is right, it is off by about 20.0f or so. Any help would be appreciated.
Solved it, I
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return interfaceOrientation == UIInterfaceOrientationPortrait || interfaceOrientation == UIInterfaceOrientationLandscapeLeft || interfaceOrientation == UIInterfaceOrientationLandscapeRight;
}
in my viewController, made sure the correct orientations were in my plist and it loads fine.

When does it changes orientation to default orientation?

I'm developing an iOS application with latest SDK.
My app only supports Landscape right orientation and it only has one view controller. I set the only available orientation modifying myProject-info.plist.
I'm trying to understand why this code outputs a log for a view in Portrait mode when I only supports landscape right orientation.
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
UIView *videoView = self.videoPreviewView;
NSLog(#"Video frame: %#", NSStringFromCGRect(videoView.frame));
}
Console output:
Video frame: {{0, 0}, {320, 568}}
I'm working with AVCaptureVideoPreviewLayer and I need to set its frame, but when I do it on viewWillAppear: I get portrait values but orientation value is Landscape right.
Is there an event or anything similar that i gets triggered when orientation changes to landscape?
When does the app changes orientation to default orientation?
Go into xCode, and click the project name at the top of the screen (with an xcode LOGO underneath it).
Then reveal the iPhone/Ipad development info and select the orientations you want to be available for users :)
If you add the line:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(orientationChanged) name:UIDeviceOrientationDidChangeNotification object:nil];
then it will call the -(void)orientationChanged; method every time the orientation is changed. You can then do something like:
-(void)orientationChanged {
if (UIDeviceOrientationIsPortrait([UIApplication sharedApplication].statusBarOrientation) {
//device is portrait;
}
else {
//device is landscape;
}
}
Then you can use this to determine the layout of your views and setup based on the orientation.
EDIT:
You can also check if the orientation returned by the statusBar's orientation is equal to UIDeviceOrientationLandscapeLeft/LandscapeRight/Portrait/PortraitUpsideDown.

iPad Landscape messing up touches began

My app is only allowable in the landscape orientations, and it launches in landscape. Thus, the top left corner when the iPad is landscape is (0,0), so everything works fine.
However, when I pick up "touchesBegan"...it isn't working properly. Only when I tap on like the right two-thirds of the iPad does it pick up the touches. I know it has something to do with the orientation, because the app is literally just blank screen with a single UIView and nothing else. It is quite simple. There are no other possible problems that would cause this.
To be sepecific, if I print out the x location in the touchesBegan function, and if the iPad is held with the home button on the left, the width is 1024. And 1024-768 is 256. This is exactly the x position where it begins to sense my touches. Anything to the left of x=256 does not sense the touches.
How do I fix this?
Check Struts and Springs and make sure that whatever should pick up the touches is covering the whole area and locked to the 4 sides.
To do it programmatically,
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
CGRect appFrame = [[UIScreen mainScreen] applicationFrame];
[self.view setFrame:CGRectMake(0.0f, 0.0f, appFrame.size.width, appFrame.size.height)];
return YES;
// if you want to support only LANDSCAPE mode, use the line below
/*
return (interfaceOrientation == UIInterfaceOrientationLandscapeLeft | UIInterfaceOrientationLandscapeRight);
*/
}
This sets the view to occupy the full screen.
The answer is that, when defining the UIWindow, it needs to be defined as
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
and not strict coordinates.

Resources