My app utilizes both landscape mode and portrait mode and the user can switch between the two at will.
When a view controller is presented by a parent view controller that is in portrait orientation, the opened view controller will have the correct width & frame.
However, if the parent view controller is in landscape mode, then on IOS6 (it works correctly on IOS7), the child view controller will be too large and actually a little too short also when it is presented.
Note this is not because the values are reported incorrectly since [[UIScreen mainScreen] bounds] reports the same values regardless of the orientation the child controller is loaded in.
Any ideas on how to fix this / why this is happening? Any idea on how to force the IOS6 versions to behave like IOS7 is now behaving natively? Many thanks!!!
Edit::
Here's how the vc's are presented:
AppDelegate
Launch1 *launch1 =[[Launch1 alloc] init];
self.window.rootViewController = launcher;
[self.window makeKeyAndVisible];
Launch1 class
Search *search = [[Search alloc] init];
[self presentViewController:search animated:YES completion:nil];
Search class
//load list_container
views = [[Search_custom_views alloc] initWithControllerContext:self];
[self.view addSubview:views];
Search_custom_views UIView extension:
- (id)initWithControllerContext:(UIViewController*)contextstart {
//set size of the screen
CGRect screenRect = [[UIScreen mainScreen] bounds];
self = [super initWithFrame:screenRect];
if (self) {
....
So this was a tough one. I load all my views programmatically. They basically are UIView subclasses that correspond to each view controller. For some reason, when an IOS6 view controller is opened from a parent view controller in landscape mode, the child view controller's bounds are not immediately passed on the child vc's UIView subclasses (if you just use addSubview in the viewDidLoad method of the controller--it is not enough). IOS7 does not have this problem.
The fix for IOS6 for me was doing the following in the viewDidLoad method of the child view controller:
//add view subclass to view controller
[self.view addSubview:views];
//reset bounds & refresh layout for IOS6
if ([[[UIDevice currentDevice] systemVersion] floatValue] < 7) {
views.frame = self.view.bounds;
[views layoutIfNeeded];
}
iOS 7 likes it when you call [[UIScreen mainScreen] bounds] instead of [[UIScreen mainScreen] applicationFrame] because the applicationFrame property is not consistently calculated between different versions of iOS, while bounds is.
It should be backwards compatible, so you should be able to do something like this:
- (CGRect)filterBankFrameForOrientation:(UIInterfaceOrientation)orientation {
CGRect appFrame = [[UIScreen mainScreen] bounds];
if (UIInterfaceOrientationIsLandscape(orientation)) {//using bounds here instead of applicationFrame for iOS7 compatibility
//Handle landscape orientation
filterBankFrame = CGRectMake(0.0, k_filterBankOffsetFromTop, appFrame.size.height, k_filterBankHeight);
}
else {
//Handle portrait orientation
filterBankFrame = CGRectMake(0.0, k_filterBankOffsetFromTop, appFrame.size.width, k_filterBankHeight);
}
return filterBankFrame;
}
and simply flip the height and width values as needed (since bounds will always be in "portrait" orientation)
Using bounds should give you the consistency you need for identical behavior across iOS versions and device sizes.
Updating in response to OP's updated code
One approach I'd recommend you at least consider is wiring up these views in InterfaceBuilder and using AutoLayout to worry about the rotations for you. It has the added benefit of gracefully handling ALL of the different screen sizes available too, so that can be nice too.
Still, creating and managing it all in code is perfectly acceptable too, and may be the right call for your situation. If so, you'll want to override a few of the rotation handling methods of UIViewController. Probably most or all of these:
– shouldAutorotate
– supportedInterfaceOrientations
– preferredInterfaceOrientationForPresentation
– willRotateToInterfaceOrientation:duration:
- didRotateFromInterfaceOrientation
at a minimum the first one and the last two.
To avoid being tied to one orientation only at launch, it is a common design pattern (citation needed) to write a method like the one I posted above, and then utilize it from both viewDidLoad as well as from the willRotate / didRotate class methods.
When calling during viewDidLoad, you do something like this:
_filterBank.collectionView.frame = [self filterBankFrameForOrientation:[[UIApplication sharedApplication] statusBarOrientation]];
which uses the statusBarOrientation property to launch correctly in either landscape or portrait.
The willRotate / didRotate methods both give you a parameter you can pass to your frame generating method.
Your method gives you the right frame size, then it's all up to you to pass down this info and manipulate your view hierarchy accordingly. It's easier than it sounds...
(in your case, it looks like launcher would implement the methods, then coordinate the adjustments to launch1 and then down to search and finally to Search_custom_views)
(**last side note, you'll make more friends here by choosing SearchCustomViews instead of Search_custom_views)
Related
The situation arises when my iPhone/iPad is to be connected to an external display.
In the normal situation, the entire device's screen gets mirrored to the external display. But, I need the screen on the external display to display content that is different from that in the device, for a specific view/page.
I have a UIImageView inside a UIScrollView, and some related buttons (next, previous, etc) as part of a specific View Controller of the app. I want the external device to display only the content in the UIScrollView (that is, the buttons are not to be shown). For this, it seems like I have to create another instance of UIWindow for the external display screen.
But, how can I make the UIWindow (and it's content) in the external display to respond correspondingly to the changes made to the main UIWindow (the one which is displayed in the iPhone/iPad). That is, changes like zooming in and zooming out.
You can do somthing like this:
- (void)checkForExistingScreenAndInitializeIfPresent
{
if ([[UIScreen screens] count] > 1)
{
// Get the screen object that represents the external display.
UIScreen *secondScreen = [[UIScreen screens] objectAtIndex:1];
// Get the screen's bounds so that you can create a window of the correct size.
CGRect screenBounds = secondScreen.bounds;
self.secondWindow = [[UIWindow alloc] initWithFrame:screenBounds];
self.secondWindow.screen = secondScreen;
// Set up initial content to display...
secondWindow.rootViewController = [ExternalScreenViewController sharedExternalScreen];
// Show the window.
self.secondWindow.hidden = NO;
}
}
Now all you need to do is create the ExternalScreenViewController with a UIView that fills the entire viewController. Finally you just set the view of the target equal to the view of the scrollView.
On iPad, I have perfectly working UISplitViewController.
I can hide and show its primaryViewController, and splitViewController:willChangeToDisplayMode: is called in appropriate way.
But on iPhone, something is wrong.
I can show primaryViewController, but cannot hide it, because the primaryViewController appears in full screen size. It's so full that I can't touch the secondary view, in that way I can hide the primaryViewController on iPad.
splitViewController:willChangeToDisplayMode: is not called either.
I have a viewDidLoad below, in my custom UISplitViewController class.
// UISplitViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.delegate = self;
self.preferredPrimaryColumnWidthFraction = .1;
CGRect mainScreen = [[UIScreen mainScreen] bounds];
self.minimumPrimaryColumnWidth = 270;
self.maximumPrimaryColumnWidth = mainScreen.size.width - 5;
}
On iPhone, any of these property seems not to be working: preferredPrimaryColumnWidthFraction or minimum/maximumPrimaryColumnWidth
I am adding this splitViewController as rootViewController in AppDelegate.m by the code below.
// AppDelegate.m
[_splitViewCon addChildViewController: tagNaviCon];
[_splitViewCon addChildViewController: mainNaviCon];
self.window.rootViewController = _splitViewCon;
I searched web and found some keywords like "container view".
Is this something I have to do with, when I want to use a UISplitViewController on iPhone ?
I also watched WWDC Video, but I didn't understand "how to code it exactly".
Currently, I do not use any Interface Builder. So I'd be rather glad if someone gives programmaticaly way to code it.
Thank you !
You can have side-by-side UISplitViewController on iPhones 4S, 5, 5S and 6 as well.
To do it you have to embed its view in another view controller (addChildViewController:...didMoveToParentViewController:)
After that you'll be able to control split's behaviour by overriding its trait collection (setOverrideTraitCollection:forChildViewController:). Basically here you have to inspect your current trait collection and change the horizontal size class to regular. This way the UISplitController will be able to show both master and detail views (primary and secondary now called) by setting split's preferredDisplayMode
Then on rotation you can make the same observations about your trait collection and change the preferredDisplayMode and override again if necessary the split's trait collection. This can be done in viewWillTransitionToSize:withTransitionCoordinator: or willTransitionToTraitCollection:withTransitionCoordinator:. The second one won't be called on an iPad as its size classes are alway regular on both orientations.
One note about a problem I'm still not able to resolve. On iPhone 5S for example when rotating in portrait I'm hiding the master controller so to have only one view on the screen and the UISplitViewController should adapt itself to a UINavigationController. That works fine however during the rotation animation the master view is disappearing and you can see a blank ugly space.
One other note as well.
You have to implement UISplitViewControllerDelegate and use methods in order to set which view controller should be visible on app launch and when split is used as a navigation.
Here is a thread about this.
Hope it helps and if I find solution about the problem I have I'll update my answer
The #user1006806 answer worked for me. Here's how I got rid of the ugly blank space during the rotation from within my UISplitViewController's rotation method (iOS 8):
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
UIInterfaceOrientation theOrientation = [[UIApplication sharedApplication] statusBarOrientation];
if (UIInterfaceOrientationIsPortrait(theOrientation)) {
self.preferredDisplayMode = UISplitViewControllerDisplayModeAllVisible;
} else {
self.preferredDisplayMode = UISplitViewControllerDisplayModeAutomatic;
}
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
} completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
self.preferredDisplayMode = UISplitViewControllerDisplayModeAutomatic;
}];
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
}
InterfaceOrientation of UIViewController is deprecated and the Document suggest to use [[UIApplication sharedApplication] statusBarOrientation], but there is no sharedApplication in an extension of iOS 8.
As #Retro mentioned, in most circumstances, you can use self.traitCollection.verticalSizeClass or self.traitCollection.horizontalSizeClass in a UIViewController to get orientation information.
A UITraitCollection object provides details about the characteristics of a UIViewController object, which manages a set of views that make up a portion of your app’s interface. These characteristics, or traits, define the size class, display scale, and device idiom of the view controller. When a view controller is created, a trait collection is automatically created for that view controller.
You can create and modify a view controller’s trait collection to customize your app. The following methods create a new trait collection containing only the passed parameter:
traitCollectionWithDisplayScale:
traitCollectionWithUserInterfaceIdiom:
traitCollectionWithHorizontalSizeClass:
traitCollectionWithVerticalSizeClass:
Non-deprecated and will work on any device screen size (including future screen sizes Apple will be releasing this year).
-(void)viewDidLayoutSubviews {
NSLog(#"%#", self.view.frame.size.width == fminf([[UIScreen mainScreen] bounds].size.width, [[UIScreen mainScreen] bounds].size.height) ? #"Portrait" : #"Landscape");
}
Thanks #anneblue for helping shorten the code!
You can convert screen bounds to keyboard view's coordinate system. After that you can check what is bigger width or height.
Simple - just use:
[[UIApplication sharedApplication] statusBarOrientation]
I need to add the ads functionality in my iOS App. And ads screen would appear after some time interval. My whole is in Landscape mode only. When I tried to add the view on current view then it shows the views in portrait mode not in landscape mode. I have set the view frame i.e. CGSizeMake(0,0, 568, 320)
time = [NSTimer scheduledTimerWithTimeInterval:2.0f
target:self
selector:#selector(showfirstad)
userInfo:nil
repeats:YES];
-(void)showfirstad {
[[[[UIApplication sharedApplication] windows] lastObject] addSubview:firstad];
}
It appears like this .
_window = [UIApplication sharedApplication].keyWindow;
if (!_window) _window = [[UIApplication sharedApplication].windows objectAtIndex:0];
UIInterfaceOrientation orientation = self.window.rootViewController.interfaceOrientation;
// Set appropriate view frame (it won't be autosized by addSubview:)
CGRect appFrame = [[UIScreen mainScreen] applicationFrame];
if (UIInterfaceOrientationIsLandscape(orientation))
{
// Need to flip the X-Y coordinates for landscape
self.view_login.frame = CGRectMake(appFrame.origin.y, appFrame.origin.x, appFrame.size.height, appFrame.size.width+20);
else
{
self.view_login.frame = appFrame;
}
[[[_window subviews] objectAtIndex:0] addSubview:self.view_login];
The reason your UIView gets displayed in portrait orientation while the rest of your app gets displayed in landscape is because you are adding the UIView 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:UIView];, do something like [self.view addSubview:UIView];
I had the same issues with rotation and autolayots when used addSubview:myView.
I managed to solve this problem by using standard container controllers or placing views directly to storyboard.
You can probably just add the view that will keep your ad into the screen in storyboard and then set hidden property to YES. Then you can change it to YES after some time.
What I tried so far is, in viewDidLoad, I called
self.bannerView.autoresizingMask=UIViewAutoresizingFlexibleWidth;
and
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)newInterfaceOrientation duration:(NSTimeInterval)duration {
if (newInterfaceOrientation == UIInterfaceOrientationLandscapeLeft || newInterfaceOrientation == UIInterfaceOrientationLandscapeRight) {
self.bannerView.frame=CGRectMake(0.0,
0.0,
480.0,
GAD_SIZE_320x50.height);
}
// Position the UI elements for portrait mode
else {
self.bannerView.frame=CGRectMake(0.0,
0.0,
GAD_SIZE_320x50.width,
GAD_SIZE_320x50.height);
}
}
Both of these didn't work for me.
Hmm, I don't think that AdMob's creatives can stretch to fit the size of the screen when in landscape. So despite the fact that you're stretching the frame of the view to fit, the ad itself I think will stay the same size.
This means you should still see an ad come in on orientation changes, it will just look like it's the same size (make sure to make another request for an ad in the willAnimateRotationToInterfaceOrientation: method to see this).
You don't need to do any moves, but you must set correct rootViewController for adMovView.
If you use view controller model please add line in each custom view controller
adMobView.rootViewController = viewController;
where viewController - root view controller of your app.
Do not code like this
adMobView.rootViewController = self;
in custom view!