I have a UINavigationController as the root view controller of my UIWindow on iOS 7 and iOS 8. From one of its view controllers, I present a fullscreen modal view controller with a cross-dissolve presentation style. This modal view controller should be able to rotate to all orientations, and it works fine.
The problem is when the device is held in a landscape orientation and the modal view controller is dismissed. The view controller which presented the modal only supports portrait orientation, and I've confirmed that UIInterfaceOrientationMaskPortrait is returned to -application:supportedInterfaceOrientationsForWindow:. -shouldAutorotate returns YES, as well. However, the orientation of the presenting view controller, after dismissing the modal, remains landscape. How can I force it to remain in portrait orientation while allowing the modal to take the orientation of the device? My code follows:
App delegate:
- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
UINavigationController *navigationController = (UINavigationController *)self.deckController.centerController;
NSArray *viewControllers = [navigationController viewControllers];
UIViewController *top = [viewControllers lastObject];
if (top && [top presentedViewController]) {
UIViewController *presented = [top presentedViewController];
if ([presented respondsToSelector:#selector(isDismissing)] && ![(id)presented isDismissing]) {
top = presented;
}
}
return [top supportedInterfaceOrientations];
}
return (UIInterfaceOrientationMaskLandscapeLeft|UIInterfaceOrientationMaskLandscapeRight);
}
Presenting view controller:
- (BOOL)shouldAutorotate {
return YES;
}
- (NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskPortrait;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return UIInterfaceOrientationPortrait;
}
Modal view controller:
- (BOOL)shouldAutorotate
{
return YES;
}
- (NSUInteger)supportedInterfaceOrientations
{
return (UIInterfaceOrientationMaskLandscape|UIInterfaceOrientationMaskLandscapeLeft|UIInterfaceOrientationMaskPortrait);
}
If the modal controller was in landscape orientation before dismissal, the presenting ViewController may not return to the origin orientation (portrait). The problem is because the AppDelegate supportedInterfaceOrientationsForWindow method is called before the controller is actually dismissed and the presented controller check still returns Landscape mask.
Set a flag to indicate whether the (modal) presented view controller will be displayed or not.
- (void)awakeFromNib // or where you instantiate your ViewController from
{
[super awakeFromNib];
self.presented = YES;
}
- (IBAction)exitAction:(id)sender // where you dismiss the modal
{
self.presented = NO;
[self dismissViewControllerAnimated:NO completion:nil];
}
And in the modal presented ViewController set the orientation according to the flag: When the modal ViewController is presented - return Landscape. When it is dismissed then return portrait
- (NSUInteger)supportedInterfaceOrientations
{
if ([self isPresented]) {
return UIInterfaceOrientationMaskLandscape;
} else {
return UIInterfaceOrientationMaskPortrait;
}
}
Last step - from your AppDelegate call the modal presented ViewController for its orientation. I am just checking the currently presented ViewController and call the supportedInterfaceOrientations on it
- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
NSUInteger orientationMask = UIInterfaceOrientationMaskPortrait;
UIViewController *currentVC = self.window.rootViewController.presentedViewController; // gets the presented VC
orientationMask = [currentVC supportedInterfaceOrientations];
return orientationMask;
}
For more info check this link
This solution is for iOS 8+.
Problem description
Application key window have UINavigationController's subclass as its rootViewController.
This NC subclass prohibits some of the interface orientations.
Some View Controller (VC1) in the NC stack is presenting another View Controller (VC2) modally and fullscreen.
This presented VC2 allows more interface orientations than NC do.
User rotates device to orientation that is prohibited by NC, but allowed by presented VC2.
User dismisses the presented VC2.
View of VC1 has incorrect frame.
Setup and illustration
UINavigationController's subclass:
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
- (BOOL)shouldAutorotate
{
return YES;
}
VC1 initial appearance and UI view stack:
Presenting VC2 (QLPreviewController in that example) from VC1:
QLPreviewController *pc = [[QLPreviewController alloc] init];
pc.dataSource = self;
pc.delegate = self;
pc.modalPresentationStyle = UIModalPresentationFullScreen;
[self.navigationController presentViewController:pc animated:YES completion:nil];
VC2 is presented and device rotated to landscape:
VC2 dismissed, device is back in portrait mode, but NC stack remains in landscape:
Cause
Apple documentation states:
When you present a view controller using the presentViewController:animated:completion: method, UIKit always manages the presentation process. Part of that process involves creating the presentation controller that is appropriate for the given presentation style.
Apparently there is a bug in handling UINavigationController stack.
Solution
This bug can be bypassed by providing our own transitioning delegate.
BTTransitioningDelegate.h
#import <UIKit/UIKit.h>
#interface BTTransitioningDelegate : NSObject <UIViewControllerTransitioningDelegate>
#end
BTTransitioningDelegate.m
#import "BTTransitioningDelegate.h"
static NSTimeInterval kDuration = 0.5;
// This class handles presentation phase.
#interface BTPresentedAC : NSObject <UIViewControllerAnimatedTransitioning>
#end
#implementation BTPresentedAC
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return kDuration;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)context
{
// presented VC
UIViewController *toVC = [context viewControllerForKey:UITransitionContextToViewControllerKey];
// presented controller ought to be fullscreen
CGRect frame = [[[UIApplication sharedApplication] keyWindow] bounds];
// we will slide view of the presended VC from the bottom of the screen,
// so here we set the initial frame
toVC.view.frame = CGRectMake(frame.origin.x, frame.origin.y + frame.size.height, frame.size.width, frame.size.height);
// [context containerView] acts as the superview for the views involved in the transition
[[context containerView] addSubview:toVC.view];
UIViewAnimationOptions options = (UIViewAnimationOptionCurveEaseOut);
[UIView animateWithDuration:kDuration delay:0 options:options animations:^{
// slide view to position
toVC.view.frame = frame;
} completion:^(BOOL finished) {
// required to notify the system that the transition animation is done
[context completeTransition:finished];
}];
}
#end
// This class handles dismission phase.
#interface BTDismissedAC : NSObject <UIViewControllerAnimatedTransitioning>
#end
#implementation BTDismissedAC
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return kDuration;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)context
{
// presented VC
UIViewController *fromVC = [context viewControllerForKey:UITransitionContextFromViewControllerKey];
// presenting VC
UIViewController *toVC = [context viewControllerForKey:UITransitionContextToViewControllerKey];
// inserting presenting VC's view under presented VC's view
toVC.view.frame = [[[UIApplication sharedApplication] keyWindow] bounds];
[[context containerView] insertSubview:toVC.view belowSubview:fromVC.view];
// current frame and transform of presented VC
CGRect frame = fromVC.view.frame;
CGAffineTransform transform = fromVC.view.transform;
// determine current presented VC's view rotation and assemble
// target frame to provide naturally-looking dismissal animation
if (transform.b == -1) {
// -pi/2
frame = CGRectMake(frame.origin.x + frame.size.width, frame.origin.y, frame.size.width, frame.size.height);
} else if (transform.b == 1) {
// pi/2
frame = CGRectMake(frame.origin.x - frame.size.width, frame.origin.y, frame.size.width, frame.size.height);
} else if (transform.a == -1) {
// pi
frame = CGRectMake(frame.origin.x, frame.origin.y - frame.size.height, frame.size.width, frame.size.height);
} else {
// 0
frame = CGRectMake(frame.origin.x, frame.origin.y + frame.size.height, frame.size.width, frame.size.height);
}
UIViewAnimationOptions options = (UIViewAnimationOptionCurveEaseOut);
[UIView animateWithDuration:kDuration delay:0 options:options animations:^{
// slide view off-screen
fromVC.view.frame = frame;
} completion:^(BOOL finished) {
// required to notify the system that the transition animation is done
[context completeTransition:finished];
}];
}
#end
#implementation BTTransitioningDelegate
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
return [[BTPresentedAC alloc] init];
}
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
return [[BTDismissedAC alloc] init];
}
#end
Import that transitioning delegate in presenting VC:
#import "BTTransitioningDelegate.h"
Store a strong reference to an instance:
#property (nonatomic, strong) BTTransitioningDelegate *transitioningDelegate;
Instantiate in -viewDidLoad:
self.transitioningDelegate = [[BTTransitioningDelegate alloc] init];
Call when appropriate:
QLPreviewController *pc = [[QLPreviewController alloc] init];
pc.dataSource = self;
pc.delegate = self;
pc.transitioningDelegate = self.transitioningDelegate;
pc.modalPresentationStyle = UIModalPresentationFullScreen;
[self.navigationController presentViewController:pc animated:YES completion:nil];
I ended up subclassing the UINavigationController and overriding its rotation methods. The following solution works on iOS 7, but I believe there is a bug in iOS 8 beta 5 that causes the presenting view controller's view to shrink to half the screen-height after dismissing the modal in landscape orientation.
UINavigationController subclass:
- (BOOL)shouldAutorotate
{
return NO;
}
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
return UIInterfaceOrientationPortrait;
}
Related
I am trying to achieve a modal presentation effect where the presented view covers the parent view only partially as shown in the picture below.
I know I could achieve this by implementing custom transitions using UIPresentationController. I don't want to reinvent the wheel so before I roll on with development I would like to ask.
Is there a build in support for this kind of transition in the APIs?
I researched all available Modal Presentation Styles and it appears to me there is no support for the transition I want to make and the only way of achieving it is just to code it.
I ran into this exact same issue. I went down the modal presentation styles route as well and kept hitting a wall (specifically getting it working on an iPhone rather than an iPad).
After some digging around, I was able to get it working though. Here's how I did it:
To start, we need a view controller that we will be presenting (the modal one) to set it's view's background color to transparent and set the frame of the navigation controller's view to some offset.
ModalViewController.h
#import UIKit;
#class ModalViewController;
#protocol ModalViewControllerDelegate <NSObject>
- (void)modalViewControllerDidCancel:(ModalViewController *)modalViewController;
#end
#interface ModalViewController : UIViewController
#property (weak, nonatomic) id<ModalViewControllerDelegate> delegate;
- (instancetype)initWithRootViewController:(UIViewController *)rootViewController;
#end
ModalViewController.m
static const CGFloat kTopOffset = 50.0f;
#implementation ModalViewController {
UINavigationController *_navController;
}
- (instancetype)initWithRootViewController:(UIViewController *)rootViewController
{
self = [super initWithNibName:nil bundle:nil];
if (self) {
rootViewController.navigationItem.leftBarButtonItem = [self cancelButton];
_navController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
self.view.backgroundColor = [UIColor clearColor];
[self.view addSubview:_navController.view];
// this is important (prevents black overlay)
self.modalPresentationStyle = UIModalPresentationOverFullScreen;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
CGRect bounds = self.view.bounds;
_navController.view.frame = CGRectMake(0, kTopOffset, CGRectGetWidth(bounds), CGRectGetHeight(bounds) - kTopOffset);
}
- (UIBarButtonItem *)cancelButton
{
return [[UIBarButtonItem alloc] initWithTitle:#"Cancel" style:UIBarButtonItemStylePlain target:self action:#selector(cancelButtonClicked:)];
}
- (void)cancelButtonClicked:(id)sender
{
[_delegate modalViewControllerDidCancel:self];
}
#end
Next, we need to set up the presenting controller to run the following animation:
Scale itself down
Fade out a lil' bit
Present the modal view controller using presentViewController:animated:completion
This is what I did
PresentingViewController.m
static const CGFloat kTransitionScale = 0.9f;
static const CGFloat kTransitionAlpha = 0.6f;
static const NSTimeInterval kTransitionDuration = 0.5;
#interface PresentingViewController <ModalViewControllerDelegate>
#end
#implementation PresentingViewController
...
...
- (void)showModalViewController
{
self.navigationController.view.layer.shouldRasterize = YES;
self.navigationController.view.layer.rasterizationScale = [UIScreen mainScreen].scale;
UIViewController *controller = // init some view controller
ModalViewController *container = [[ModalViewController alloc] initWithRootViewController:controller];
container.delegate = self;
__weak UIViewController *weakSelf = self;
[UIView animateWithDuration:kTransitionDuration animations:^{
weakSelf.navigationController.view.transform = CGAffineTransformMakeScale(kTransitionScale, kTransitionScale);
weakSelf.navigationController.view.alpha = kTransitionAlpha;
[weakSelf presentViewController:container animated:YES completion:nil];
} completion:^(BOOL finished) {
weakSelf.navigationController.view.layer.shouldRasterize = NO;
}];
}
#pragma mark - ModalViewControllerDelegate
- (void)modalViewControllerDidCancel:(ModalViewController *)modalViewController
{
__weak UIViewController *weakSelf = self;
[UIView animateWithDuration:kTransitionDuration animations:^{
weakSelf.navigationController.view.alpha = 1;
weakSelf.navigationController.view.transform = CGAffineTransformIdentity;
[weakSelf dismissViewControllerAnimated:YES completion:nil];
}];
}
#end
pretty sure its done like this
let newVC = <view controller you want to display>
let nav: UINavigationController = UINavigationController(rootViewController: newVC)
if let currVc = UIApplication.sharedApplication().keyWindow?.rootViewController {
nav.transitioningDelegate = currVc
nav.modalPresentationStyle = UIModalPresentationStyle.Custom;
currVc.presentViewController(nav, animated: true, completion: nil)
}
I'm pretty sure this is your answer - Page sheet - as in UIModalPresentationPageSheet
https://developer.apple.com/library/ios/documentation/userexperience/conceptual/mobilehig/Alerts.html#//apple_ref/doc/uid/TP40006556-CH14-SW3
I am subclassing a UINavigationController to serve as the main root controller of my application. The purpose of the view controller is to present a custom splash screen that allows for a swipe action from (right to left) to enter the main application. The custom transition fades the splash view during the swipe and reveals the main application underneath (a UITabBarController). Most of my code is following the objc.io article on custom view controller transitions in iOS7: http://www.objc.io/issue-5/view-controller-transitions.html.
Everything seems to work as expected. However, very rarely, I am getting reports of a black screen appearing once the splash screen disappears, instead of the main screen. I have been unable to reproduce the behavior. Here is the code:
First, setting up the main root view controller in the custom app delegate:
- (void)applicationDidFinishLaunching:(UIApplication *)application
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.rootViewController = [[CustomRootViewController alloc] initWithNavigationBarClass:nil toolbarClass:nil];
self.window.rootViewController = self.rootViewController;
[self.window makeKeyAndVisible];
}
Important portions from CustomRootViewController (note I'm hiding the navigation bar):
- (instancetype) initWithNavigationBarClass:(Class)navigationBarClass toolbarClass:(Class)toolbarClass
{
self = [super initWithNavigationBarClass:navigationBarClass toolbarClass:toolbarClass];
if (self) {
self.navigationBarHidden = YES;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.delegate = self;
// This is the main UI for the app
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"Main" bundle:[NSBundle mainBundle]];
CustomTabBarControllerViewController *mainViewController = [mainStoryboard instantiateInitialViewController];
self.mainViewController = mainViewController;
UIStoryboard *splashStoryboard = [UIStoryboard storyboardWithName:#"Splash" bundle:[NSBundle mainBundle]];
SplashViewController *splashViewController = [splashStoryboard instantiateInitialViewController];
self.splashViewController = splashViewController;
// Initialize the navigation controller to have the main app sitting under the splash screen
self.viewControllers = #[self.mainViewController, self.splashViewController];
}
// In the public interface, called from the Splash screen when the user can perform the action
- (void)allowSwipeToDismiss
{
UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
panGestureRecognizer.delegate = self;
[self.view addGestureRecognizer:panGestureRecognizer];
}
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC
{
if (operation == UINavigationControllerOperationPop) {
return [[CustomRootViewControllerAnimator alloc ] init];
}
return nil;
}
- (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController
{
return self.interactiveController;
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
// Only enable swiping when on the Swipe To Enter screen
// After we are in the main app, we don't want this gesture recognizer interfering with the rest of the app
if (self.topViewController == self.splashViewController) {
return true;
} else {
return false;
}
}
- (void)handlePan:(UIPanGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateBegan) {
self.interactiveController = [[UIPercentDrivenInteractiveTransition alloc] init];
[self popViewControllerAnimated:YES];
} else if (recognizer.state == UIGestureRecognizerStateChanged) {
CGPoint translation = [recognizer translationInView:self.view];
CGFloat viewWidth = self.view.bounds.size.width;
CGFloat percentDone = -translation.x / viewWidth;
[self.interactiveController updateInteractiveTransition:percentDone];
} else if (recognizer.state == UIGestureRecognizerStateEnded) {
if (self.interactiveController.percentComplete < 0.5) {
[self.interactiveController cancelInteractiveTransition];
} else {
[self.interactiveController finishInteractiveTransition];
}
self.interactiveController = nil;
}
}
And here is the code for the CustomRootViewControllerAnimator being returned by my custom UINavigationController:
#implementation CustomRootViewControllerAnimator
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
return 1;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
[[transitionContext containerView] insertSubview:toViewController.view
belowSubview:fromViewController.view];
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
CGFloat screenWidth = [[UIScreen mainScreen] bounds].size.width;
fromViewController.view.transform = CGAffineTransformMakeTranslation(-screenWidth, 0);
fromViewController.view.alpha = 0;
} completion:^(BOOL finished) {
fromViewController.view.transform = CGAffineTransformIdentity;
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
#end
Was finally able to replicate the problem, but only in iOS 7.0. By starting the interactive transition, then canceling it, I was able to get the black screen on the next start of the transition. In the completion block, I needed to set the alpha value of the fromViewController back to 1. Again, this is only necessary in iOS 7.0. It does not happen in 7.1.
My app is a portrait only. I need to present a UIViewController in Landscape mode(Reason for that is I am using Core-Plot sdk for drawing graphs on that viewcontroller, so it needs to be in landscape mode).
I tried the following methods and it work fine. But the issue is, when I dismiss the landscape viewcontroller, app cannot force to portrait mode.
http://www.sebastianborggrewe.de/only-make-one-single-view-controller-rotate/
http://b2cloud.com.au/tutorial/forcing-uiviewcontroller-orientation/
How can I force the app to become portrait only mode after dismiss the landscape viewcontroller?
This is How I presenting the landscape view controller and dismissing it.
LineChartViewController *obj = [[LineChartViewController alloc]initWithNibName:#"LineChartViewController" bundle:nil];
[self.navigationController presentViewController:obj animated:YES completion:nil];
- (IBAction)btnDonePressed:(id)sender{
[self dismissViewControllerAnimated:NO completion:nil];
}
XIB of the LineChartViewController is in landscape mode.
In simple words, My app is portrait only, and I wanted to show CorePlot hostView in landscape mode.
Actually I could solve the issue by rotating the CorePlot hostView. The solution isn't the perfect for the the question described, but I'd like to put my solution here, since it solved my problem
self.hostView = [(CPTGraphHostingView *) [CPTGraphHostingView alloc] initWithFrame:self.viewGraphBack.bounds];
self.hostView.allowPinchScaling = YES;
[self.viewGraphBack addSubview:self.hostView];
CGAffineTransform transform =
CGAffineTransformMakeRotation(DegreesToRadians(90));
viewGraphBack.transform = transform;
viewGraphBack.frame = CGRectMake(-285, 0, 568, 320);
[self.view addSubview:viewGraphBack];
This kind of workaround works for me (temporary pushing fake model view controller), called after other view controller which introduces new orientation is demised:
- (void)doAWorkaroudnOnUnwantedRotation {
// check if is workaround nesesery:
if (UIInterfaceOrientationIsLandscape([UIDevice currentDevice].orientation)) {
double delayInSeconds = 0.7;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
UIViewController *fake = [[[UIViewController alloc] init] autorelease];
UIViewController *rootController = [UIApplication sharedApplication].keyWindow.rootViewController;
[rootController presentModalViewController: fake animated: NO];
[fake dismissModalViewControllerAnimated: NO];
});
}
}
If Parent viewcontroller in UIInterfaceOrientationPortrait mode & current viewcontroller should be in UIInterfaceOrientationLandscapeLeft or UIInterfaceOrientationLandscapeRight in that case you may use device orientation changing delegate & a few codes in viewdidload
In ViewDidLoad
NSNumber *value = [NSNumber numberWithInt:UIInterfaceOrientationLandscapeLeft];
[[UIDevice currentDevice] setValue:value forKey:#"orientation"];
& implement shouldAutorotateToInterfaceOrientation delegate in ViewController
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
if (UIInterfaceOrientationIsLandscape(interfaceOrientation)) return YES;
return NO;
}
Like this. Assure that landscape mode must be checked in from target settings.
Write this category in your project
#import "UINavigationController+ZCNavigationController.h"
#implementation UINavigationController (ZCNavigationController)
-(BOOL)shouldAutorotate
{
return [[self.viewControllers lastObject] shouldAutorotate];
}
-(NSUInteger)supportedInterfaceOrientations
{
return [[self.viewControllers lastObject] supportedInterfaceOrientations];
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
return [[self.viewControllers lastObject] preferredInterfaceOrientationForPresentation];
}
#end
And in your viewcontroller where you need Landscape
- (BOOL)shouldAutorotate {
return YES;
}
- (NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskLandscape;
}
The following is my custom VC presentation code:
-(void)presentViewController:(UIViewController*)vc
{
UIWindow *w = [[[UIApplication sharedApplication] delegate] window];
UIViewController *parentController = (TabBarViewController *)[w rootViewController];
[parentController addChildViewController:vc];
if ([vc respondsToSelector:#selector(beginAppearanceTransition:animated:)]) // iOS 6
{
[vc beginAppearanceTransition:YES animated:YES];
}
UIView *toView = vc.view;
[parentController.view addSubview:toView];
toView.frame = parentController.view.bounds;
CGAffineTransform tr = CGAffineTransformScale(self.view.transform, 1.0f, 1.0f);
toView.transform = CGAffineTransformScale(self.view.transform, 0.01f, 0.01f);;
CGPoint oldCenter = toView.center;
toView.center = ((RootViewControllerEx*)vc).cellCenter;
[UIView animateWithDuration:4.5 animations:^{
toView.transform = tr;
toView.center = oldCenter;
} completion:^(BOOL finished) {
[vc didMoveToParentViewController:parentController];
if ([vc respondsToSelector:#selector(endAppearanceTransition)]) // iOS 6
{
[vc endAppearanceTransition];
}
}];
}
It works fine, however, in presented VC I am hiding status bar:
- (BOOL)prefersStatusBarHidden {
return YES;
}
When I present my VC using built-in presentViewController:animated:completion:, status bar in presented VC is hidden. But with my code on iOS 7 status bar is not hidden at all, on iOS 6 it is even more strange - status bar is hidden, but my view size is shorter from top by the size of status bar. So I can see a black gap from top on iOS 6. What should I do to properly hide status bar when using custom VC presentation?
you should try this in your viewDidLoad for differencing the IOS 6/7 status bar problem
if ([self respondsToSelector:#selector(setNeedsStatusBarAppearanceUpdate)])
{
//IOS 7 - Status Bar Hidden
[self prefersStatusBarHidden];
[self performSelector:#selector(setNeedsStatusBarAppearanceUpdate)];
self.statusBarHidden = YES;
}
else
{
// iOS 6 - Status Bar shown
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide];
self.statusBarHidden = NO;
}
and an method for hiding status Bar
- (BOOL)prefersStatusBarHidden{
return YES;}
and also add an property for status Bar
#property BOOL statusBarHidden;
then make sure that your view bounds to the screen size and fits correctly
I think this solves your problem :)
Try this
in view did load
[UIApplication sharedApplication].statusBarHidden = YES;
and set value in plist like
set this in project summary
and this in your interface builder
I have a UINavigationController where all UIViewController in the stack are portrait, except the last one, which is landscape. My current implementation shows the last view controller in portrait on push, but I can rotate to landscape, and the, cannot rotate back to portrait. How can I force the rotation to landscape when the view is pushed ?
My UINavigationController subclass :
#interface RotationViewController : UINavigationController
#end
#implementation RotationViewController
- (BOOL)shouldAutorotate
{
return YES;
}
- (NSUInteger)supportedInterfaceOrientations
{
if (self.topViewController.class == [LoadingViewController class])
{
return UIInterfaceOrientationMaskLandscape;
}
return UIInterfaceOrientationMaskPortrait;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
if (self.topViewController.class == [LoadingViewController class])
{
return UIInterfaceOrientationLandscapeLeft;
}
return UIInterfaceOrientationPortrait;
}
#end
The view controller that I want landscape-only :
#implementation LoadingViewController
- (BOOL)shouldAutorotate
{
return YES;
}
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskLandscape;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
return UIInterfaceOrientationLandscapeLeft;
}
#end
Other view controllers don't implement any of these methods.
I have the same condition. So You can implement the below code in your application
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeLeft animated:NO];
CGAffineTransform landscapeTransform = CGAffineTransformMakeRotation(degreesToRadian(270));
landscapeTransform = CGAffineTransformTranslate (landscapeTransform, 0.0, 0.0);
[[self navigationController].view setTransform:landscapeTransform];
self.navigationController.view.bounds = CGRectMake(0.0, 0.0, 480, 320);
self.navigationController.navigationBar.frame = CGRectMake(0.0, 20.0, 480, 44.0);
}
-(void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait animated:NO];
CGAffineTransform landscapeTransform = CGAffineTransformMakeRotation(degreesToRadian(0));
landscapeTransform = CGAffineTransformTranslate (landscapeTransform, 0.0, 0.0);
[[self navigationController].view setTransform:landscapeTransform];
self.navigationController.view.bounds = CGRectMake(0.0, 0.0, 320, 480);
self.navigationController.navigationBar.frame = CGRectMake(0.0, 20.0, 320.0, 44.0);
}
#define degreesToRadian(X) (M_PI * (X)/180.0)
Note:-
The above code is implement for the iphone. So Please change the bounds if you use this for ipad.
You need not to implement the
- (BOOL)shouldAutorotate
- (NSUInteger)supportedInterfaceOrientations
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
functions in your view controller.... So remove those methods....
Check the question How to handle different orientations in iOS 6. See the answer there for a project example of exactly what you need.
Basically you need to embed a custom navigation controller in your viewcontroller (the one you want to rotate). Add the following method in this custom navigation controller
- (NSUInteger)supportedInterfaceOrientations
{
return self.topViewController.supportedInterfaceOrientations;
}
and add to your view controller that should rotate:
- (BOOL)shouldAutorotate
{
return YES;
}
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight;
}
Be sure Portrait, Landscape Right and Landscape Left orientations are enabled in your project. Then, if you want to block some orientations for a particular view:
– application:supportedInterfaceOrientationsForWindow: