UIView added on top of window does not rotate on iOS - ios

I have a tabBar based application and want to present some custom view above whole screen (not as modal view) and I do it like that:
[self.view.window addSubview:self.myViewController.view];
The reason I did this is because this way view is positioned above UITabBar.
Anyway view is presented nicely and it covers whole screen like I want to. But there is a problem. When I rotate device this top view does not rotate, but view's underneath do.
I've tested on iOS5 and iOS6 without luck. Have also put this code in delegate:
- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
return UIInterfaceOrientationMaskAll;
}
Similar code is in myViewController's view:
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskAll;
}
The view just doesn't rotate... ?

As far as i know only the first subview of the window gets the rotation events. You're adding another view (your view) to the window and therefore need to deal with propagating the rotation events yourself.
Providing some really quick-help for you, just check out the following implementation : AGWindowView (not maintained from 2016)

You can set the rootController for your UIWindow.
e.g:
fileprivate(set) var bottonOverlayWindow = UIWindow()
self.bottonOverlayWindow.rootViewController = self;
// 'self' will the ViewController on which you had added UIWindow view. So whenever you ViewController change the orientation, your window view also change it's orientation.
Let me know if you face any issue.

Related

UISplitViewController doesn't work on iPhone(5 nor 6)

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];
}

interface orientation not correct in presented view controllers in iOS 8

in iOS 7 and earlier, I was able to make UI changes in viewDidLayoutSubviews like so:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
if ( UIInterfaceOrientationIsLandscape(self.interfaceOrientation) ) {
//update views for landscape
}
else {
//update views for portrait
}
}
I can still do this in iOS 8 except in view controllers that are presented by other view controllers. When a view controller is presented by another view controller, this does not work, because the self.interfaceOrientation property is not correct in the presented view controller. It does not reflect the current orientation, but rather the orientation right before the device rotation.
The issue is not present if presenting a view controller on iPad with modalPresentationStyle set to UIModalPresentationPageSheet. In other words, the issue is only present when presenting a full screen view controller.
Any suggestions?
Try using this instead:
[[UIApplication sharedApplication] statusBarOrientation]
Also, if you are dropping support for iOS 7 and going 8-only, I recommend you drop interface orientation handling in favor of trait collections and size classes, if you can.

Rotating only one UIView (of the two) in a UIViewController

I have a GLKViewController with two views: a GLKView and a transparent UIView showing some commands.
The GLKViewController supports two device orientations: portrait and landscape-right.
When the device is rotated from portrait to landscape I would like to:
NOT rotate the GLKView;
rotate the overlaid UIView with the commands.
Basically the GLKView should stay always in portrait, while the other view should follow the device orientation.
The GLKViewController is in a NavigationController. Either I need to have the NavigationController rotating in landscape or I need to implement a fake Navigation Bar and perform the 'pop' programmatically.
I saw a solution which simply rotated the view (after catching a notification), but I would like to keep the animation rotating that view.
I tried to use another UIViewController subclass, receiving the GLKViewController and the View with the commands as two child UIViewControllers but it did not work: the screen stayed black.
When a UINavigationController is involved, create a category on the UINavigationController and override supportedInterfaceOrientations.
#import "UINavigationController+Orientation.h"
#implementation UINavigationController (Orientation)
-(NSUInteger)supportedInterfaceOrientations
{
return [self.topViewController supportedInterfaceOrientations];
}
-(BOOL)shouldAutorotate
{
return YES;
}
#end
Now, iOS containers (such as UINavigationController) do not consult their children to determine whether they should autorotate.
How to create a category
Add a new file (Objective c- category under cocoa touch)
Category : Orientation on UINavigationController
Add the above code to UINavigationController+Orientation.m

UIViews ending up beneath tab bar

In my app, I aligned a label the standard amount from the bottomLayoutGuide using autolayout. When the app first starts everything is layed out as I wanted but when I switch tabs and go back the label has disappeared under the tab bar controller.
If I rotate the device, the landscape view appears correctly and when I rotate it back to portrait the view is back to normal. I can't seem to figure out what is causing this behavior. Thanks for your help!
This happens due to a bug in iOS7, where the bottom layout guide is incorrectly set to height 0 instead of the tab bar's height. When you rotate the device, the bottom layout guide is set correctly.
Currently, your best option is to disable bottom extended layout:
- (UIRectEdge)edgesForExtendedLayout
{
return [super edgesForExtendedLayout] ^ UIRectEdgeBottom;
}
Do this for each view controller that is displayed from the tab bar controller. Remember to set the tab bar view controller's background color to whatever suits your application.
Make sure to open a bug report at https://bugreport.apple.com
To elaborate a little more, it seems viewDidLayoutSubviews is called twice when switching view controllers. First time, everything is set correctly, but the second time bottom layout guide height is 0. You can see from the stack trace that the first one comes from tab bar layout, while the second call is from a scheduled CALayer layout, which is incorrect.
While Leo's answer shows how to do it programmatically, if you want to do this from the interface builder, select your View Controller and uncheck "Under bottom bars" from the Extend Edges section:
Calling setNeedsLayout is all that needs to be done. This essentially patches the framework bug. It needs to be called on the UITabBarController view itself when a new view is selected. Create a delegate for the app's tab bar controller. and put this in the delegate object:
#interface MyPatch : NSObject <UITabBarControllerDelegate>
#end
#implementation MyPatch
-(void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
{
[tabBarController.view setNeedsLayout];
}
#end
And initialize it wherever you want... something like this:
#interface AppDelegate : UIResponder <UIApplicationDelegate>
{
MyPatch *patch;
}
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
patch=[MyPatch new];
myTabBarController.delegate=patch;
}
#end
Leo is right, the bottomLayoutGuide is returned incorrectly.
But unsetting the extend edges under bottom bars (or overriding edgesForExtendedLayout) had too much undesired effects on other subviews for me.
If you want to change only the constraint for one view according to the bottom layout guide,
implement viewWillLayoutSubviews and check the value of the bottomLayoutGuide property and adapt that one constraint if required, like so:
- (void)viewWillLayoutSubviews {
[self adaptBottomLayoutGuides];
}
/// Workaround for iOS7 bug returning wrong bottomLayoutGuide length if this is 1st tab in TabViewController
- (void)adaptBottomLayoutGuides {
NSLog(#"%f", self.bottomLayoutGuide.length);
CGFloat expectedHeight = 123;
CGFloat adaptedSpacing = expectedHeight - self.bottomLayoutGuide.length;
self.viewBottomLayoutSpacingConstrain.constant = adaptedSpacing;
}

call shouldAutorotateToInterfaceOrientation but lock view from rotating

I want to create a UIVIew that will not rotate when I will call to shouldAutorotateToInterfaceOrientation , and other subviews will rotate.
and i want to keep the shouldAutorotateToInterfaceOrientation suppot, and not use notification.
thanks
Be sure to define exactly what you mean by having a view "not rotate" when the device is rotated. Rotation can mean several things, depending on which coordinate system to which you refer. A better way to think about it is simply, what do you want your view to look like for each device orientation.
Just to remind, shouldAutorotateTo... is sent to your view controller by the system. You don't invoke it yourself. It doesn't cause rotation. It lets the system ask your view controller what orientations it supports.
Your VC should answer YES for all orientations it supports. A supported orientation is one where the view changes layout in response to a device orientation change, so if any layout change occurs for a given orientation, then the answer to shouldAutorotateTo is probably YES.
Altering subview layout for a given interface orientation is mostly your responsibility. Views have an autoresizingMask which is a bit vector describing some options for sizing and positioning relative to their parent, and this is often adequate. The way to fully control layout on orientation change is by implementing willAnimateRotationToInterfaceOrientation.
For example, here's a fairly permissive shouldAutorotate, enabling all but one orientation...
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
And here's how you would control how subviews layout on rotation...
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
UIView *testView = [self.view viewWithTag:16];
if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation)) {
// change frames here to make the ui appear according to your spec
// including however you define "not rotating" for each view
self.subviewA.frame = .....
self.subviewB.frame = .....
} else {
self.subviewA.frame = .....
self.subviewB.frame = .....
}
}
If you want one UIView not to Rotate with orientation, one of the easy solution is to add that view to Application top Window like this. Because window dont rotate with device orientations.
[[[[UIApplication sharedApplication]windows]objectAtIndex:0]addSubview:customView];

Resources