Problems swapping UIViewControllers when orientation changes - ios

I have a single view app with 2 view controllers to present different layouts for portrait and landscape. I have set up the orientation changed notifications and can successfully display the landscape view on the first orientation change.
1st Problem:
When I change the orientation back to portrait the portrait view is not displayed.
2nd Problem:
When I change the orientation back to landscape the landscape view displays but I get a warning:
Attempt to present CalculatorViewControllerLandscape on CalculatorViewController whose view is not in the window hierarchy.
I have been through the apple documentation and several posts with similar problems and have figured out that the answer lies in the use of delegation but I have not been able to get delegation set up correctly. Here is my attempt:
CalculatorViewControllerLandscape.h
#protocol SecondControllerDelegate <NSObject>
#end
.....
#property(nonatomic, weak) id <SecondControllerDelegate> delegate;
CalculatorViewController.h
#interface CalculatorViewController : UIViewController <SecondControllerDelegate> {
....
}
#property (strong) CalculatorViewControllerLandscape *landscapeVC;
CalculatorViewCalculator.m
- (void)awakeFromNib
{
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(orientationChanged:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
// register as a delegate
self.navigationController.delegate = (id)self;
}
- (void)orientationChanged:(NSNotification *)notification
{
UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
if (UIDeviceOrientationIsLandscape(deviceOrientation) &&
!isShowingLandscapeView)
{
NSLog(#"Orientation has changed to landscape");
// code here to show landscape storyboard
UIStoryboard *landscapeStoryboard = [UIStoryboard storyboardWithName:#"LandscapeStoryboard" bundle:nil];
UIViewController *landscapeViewController = [landscapeStoryboard instantiateInitialViewController];
[self presentViewController:landscapeViewController animated:YES completion:nil];
isShowingLandscapeView = YES;
}
else if (UIDeviceOrientationIsPortrait(deviceOrientation) &&
isShowingLandscapeView)
{
NSLog(#"Orientation has changed to portrait");
[[self presentingViewController] dismissViewControllerAnimated:NO completion:nil];
isShowingLandscapeView = NO;
}
}
I've been working on this for several hours and have checked all posts which similar problems but I still can't figure it out. Thanks in advance for help.

Best practice is to handle rotation events in a single UIViewController, rather than using two separate ones. I'm not familiar with interface builder, but programmatically you can override -(void)viewWillLayoutSubviews; and lay out your view appropriately based on self.interfaceOrientation. I recommend you do it that way.
However, in answer to your question:
try changing
[[self presentingViewController] dismissViewControllerAnimated:NO completion:nil];
to
[self dismissViewControllerAnimated:NO completion:nil];
This may fix the 2nd problem as well, because the old landscape view controller not being properly dismissed.

Related

iOS force landscape in one viewController

Sir,
I am working on the mapview module which landscape is the only orientation allowed but others for portrait only. When it comes to running on device ios 7 and 8 , the view controller is still presented as portrait orientation unless I have to manually turn the device to landscape . Would you please tell me what other steps to do ?
The below is my code
AppDelegate.h
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#property (nonatomic) BOOL isTaskPoint;
#end
AppDelegate.m
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
if (UIInterfaceOrientationIsPortrait(interfaceOrientation)) return YES;
return NO;
}
PreviousController.m
MapViewController * sliderVC = [[MapViewController alloc] init ];
AppDelegate *appDelegate = (AppDelegate *) [UIApplication sharedApplication].delegate;
appDelegate.isTaskPoint = TRUE;
sliderVC.modalPresentationStyle = UIModalPresentationCurrentContext;
[self presentViewController:sliderVC animated:NO completion:nil];
sliderVC.view.backgroundColor = [UIColor clearColor];
// MapSwift maps =
MapViewController.h
- (void)bannerTapped:(UIGestureRecognizer *)gestureRecognizer {
AppDelegate *appDelegate = (AppDelegate *) [UIApplication sharedApplication].delegate;
appDelegate.isTaskPoint = FALSE;
[self dismissViewControllerAnimated: NO completion:nil];
}
MapViewController
- (BOOL) shouldAutorotate {
return YES;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return UIInterfaceOrientationLandscapeRight;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationLandscapeRight|| interfaceOrientation == UIInterfaceOrientationLandscapeLeft);
}
There is one trick that really works.
You can access the status bar and set its orientation. That becomes active next time a view is displayed modally.
However, right after displaying modally you can remove the modally displayed veiw controller. When you do that within the same method, then the user would not noticing anyhing.
Now the device has the desired orientation. You can now safely push the view controller that you want to be in another orientation.
Do not forget to rotate it back, when returning from that view controller!
See the answer to this question. It comes with some code sniplets. Force controllers to Change their orientation in Either Portrait or Landscape
If you want to disable or enable specific orientation in some view controller then this might be helpful to you.
And if you want to open some view in specific orientation then use this in viewDidLoad
NSNumber *value = [NSNumber numberWithInt:UIInterfaceOrientationPortrait];
[[UIDevice currentDevice] setValue:value forKey:#"orientation"];

Orientation is not changed when view is changed from portrait to landscape

I have menu that is portrait only and detail view (with UIWebView) that can be portrait or landscape.
When I enter detail view and rotate device landscape and go back from that screen in that way to menu which should only be portrait then menu is in landscape orientation (along with status bar and navigation bar).
Is there a way to avoid this and force screen to be rotated to desired (supported) orientation?
Proposed solution by #kkumpavat didn't worked for me or it wasn't clear enough for me to understand it. I did more searching and found solution which uses:
// http://stackoverflow.com/questions/181780/is-there-a-documented-way-to-set-the-iphone-orientation
// http://openradar.appspot.com/radar?id=697
[[UIDevice currentDevice] performSelector:NSSelectorFromString(#"setOrientation:")
withObject:UIInterfaceOrientationPortrait];
but this was generating annoying warnings and I've swapped it with:
//force update of screen orientation
if (self.interfaceOrientation != [self preferredInterfaceOrientationForPresentation]) {
[[UIDevice currentDevice] setValue:[NSNumber numberWithInteger: [self preferredInterfaceOrientationForPresentation]]
forKey:#"orientation"];
}
called from NavigationController's viewDidAppear.
Change viewWillAppear of menu viewController as below
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
UIViewController *controller = [[UIViewController alloc] init];
[self presentViewController:controller animated:NO completion:nil];
[self dismissViewControllerAnimated:NO completion:nil];
}

Showing UINavigationController in a Modal segue (ViewController)?

I have now been stuck on this problem for more then 2 weeks! In my project, I have 1 single ViewController(slide) I want to enable both landscape and portrait in. The rest of all controllers/views(slides) I want to enable portrait-mode only.
The tricky part is, the "ViewController" I am referring to is connected to both NavigationControllers and TabBarControllers. See the scheme below where the ViewController I want to enable both landscape/portrait is named: ReferredViewController.
TabBarController ----> NavigationController ----> FristViewController --(push event)--> ReferredViewController
So far I have tried to make a CATEGORY for both NavigationControllers and TabBarControllers. But since my NavigationControllers and TabBarControllers are placed at the very start of the project this will set the rules for the whole project. My ReferredViewController is placed at the end or in the middle of the projects "storyboard". I have tried to set the rules by code aswell for the single ReferredViewController without any success.
My best shot is to change the event between FirstViewController and ReferredViewController from "push" to "modal". ReferredViewController can then rotate both portrait/landscape and the rest of the project is locked in portrait. BUT, as you may know all navigations (NavigationBar) will be lost and the user will become stuck at that single slide.
So I am trying to enable the NavigationBar with the following code example in the ReferredViewController.m file:
ShowTaskViewController *detailViewController = [[ShowTaskViewController alloc] init];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:detailViewController];
navController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self.navigationController presentModalViewController:navController animated:YES completion:nil];
[navController release];
[detailViewController release];
But ofc nothing happens and I am back to square one again :O. FML!
In this line:
[self.navigationController presentModalViewController:navController
animated:YES
completion:nil];
you are conflating two UIViewController instance methods:
- (void)presentViewController:(UIViewController *)viewControllerToPresent
animated:(BOOL)flag
completion:(void (^)(void))completion
- (void)presentModalViewController:(UIViewController *)modalViewController
animated:(BOOL)animated
The first of these is now the standard, the second method was deprecated in ios6.
Also the presenting view controller should be self (the ReferredViewController), not self's navigationController.
Your presented view controller can dismiss itself thus
[[self presentingViewController] dismissViewControllerAnimated:YES
completion:(void (^)(void))completion];
But take a look at fibnochi's answer, it may be a better way for you to achieve your result.
you have to over ride UITaBarController because it is you base view controller. I have done this for my navigation controller. Tell me if this helped.
#interface UINavigationController (Autorotation)
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation;
- (BOOL) shouldAutorotate;
- (NSUInteger) supportedInterfaceOrientations;
#end
#implementation UINavigationController (Autorotation)
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation{
if ([self.visibleViewController isKindOfClass:[MWPhotoBrowser class]] || [self.visibleViewController isKindOfClass:[ZoomPictureViewController class]]) {
return YES;
}
return (toInterfaceOrientation == UIInterfaceOrientationPortrait);
}
-(BOOL) shouldAutorotate{
return YES;
}
-(NSUInteger) supportedInterfaceOrientations{
if ([self.visibleViewController isKindOfClass:[MWPhotoBrowser class]] || [self.visibleViewController isKindOfClass:[ZoomPictureViewController class]]) {
return UIInterfaceOrientationMaskAll;
}
return UIInterfaceOrientationMaskPortrait;
}

How does presented view controller init from current orientation (especially in landscape)?

I have one view controller, named AViewController. There is a button in AViewController. When the button is clicked, i will present another view controller,lets called BViewController. If AViewController is in portrait-orientation, it works well.If the orientation is in landscape, then the BViewController is still initialized in portrait but presented in landscape.
This is the problem. I wonder how can i init BViewController in current orientation. I use [UIScreen mainScreen].bounds in the BViewController's init. but the bounds is not in landscape if the device is in landscape. I know pass the frame from AViewController to BViewController, it may work. but i don't think this is a good solution. So is there any better ways?
the function to present BViewController is as follows:
- (void)BtnClicked
{
BViewController *bvc = [[BViewController alloc] init];
bvc.delegate = self;
bvc.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentViewController:movie animated:YES completion:nil];
}
I still don't know how to do it efficiently. But i use one solution to call the willAnimateRotationToInterfaceOrientation method manually in viewDidAppear.
the snippet is as follows:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self willAnimateRotationToInterfaceOrientation:[UIApplication sharedApplication].statusBarOrientation duration:0];
}

Manually set interface orientation

Is there any easy way to manually set the orientation of an interface? I need to set the interface to portrait even though the device orientation might be in landscape during loading. Kinda want to stay away from CGAffineTransforms.
One method I know that works for me (and is a bit of a hack and can display one orientation before changing to the orientation you want) is:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
UIApplication* application = [UIApplication sharedApplication];
if (application.statusBarOrientation != UIInterfaceOrientationPortrait)
{
UIViewController *c = [[UIViewController alloc]init];
[self presentModalViewController:c animated:NO];
[self dismissModalViewControllerAnimated:NO];
[c release];
}
}
First, set your app and views to only support portrait, then use this category method taken from my refactoring library, es_ios_utils:
#interface UIViewController(ESUtils)
// Forces without using a private api.
-(void)forcePortrait;
#end
#implementation UIViewController(ESUtils)
-(void)forcePortrait
{
//force portrait orientation without private methods.
UIViewController *c = [[UIViewController alloc] init];
[self presentModalViewController:c animated:NO];
[self dismissModalViewControllerAnimated:NO];
[c release];
}
#end
The view, dismissed before the frame completes, won't be displayed.
override this to control the orientation until loading...
-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation

Resources