UIViewController device rotation delegate methods are not getting called in iOS8 - ios

I have been working an iOS 7 app to make it compatible for ios 8 (beta 5). In this application, UIViewController (vc1) presents another UIViewController (vc2). vc1 supports both Portrait and Landscape orientations; vc2 supports only Portrait orientation. When vc2 is presented, it asks vc1: shouldAutorotateToInterfaceOrientation: and this returns YES.
In iOS8 (Beta 5) willRotateToInterfaceOrientation: and didRotateFromInterfaceOrientation: are not getting called as well as the new iOS 8 API method viewWillTransitionToSize. But, this works fine in iOS7.
I know willAnimateRotationToInterfaceOrientation and didRotateFromInterfaceOrientation are deprecated in iOS 8, but even iOS 8 delegate methods are not getting called. Every time when launched vc2 from vc1 always screens loads in portrait mode only even though I mentioned supported interface orientation as landscape left.
Any ideas... is it a bug in iOS8?

Well, I didn't figure out your problem best but as soon I have a bunch of lines working fine with rotation in iOS 8.1 I will present them to you. They are just taken and a little bit of edited from the Apple API Reference.
Simply I put this in every VC and i just edit the code when needed. For example I have an app that have initial view controller in portrait and then VC changes ( segue is done ) to a LandscapeVC with different features.
This is the portrait view methods leading to a rotation in LandscapeView.
bool isShowingLandscapeView = false;
- (void)awakeFromNib
{
isShowingLandscapeView = NO;
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(orientationChanged:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
}
- (void)orientationChanged:(NSNotification *)notification
{
UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
if (UIDeviceOrientationIsLandscape(deviceOrientation) &&
!isShowingLandscapeView)
{
isShowingLandscapeView = YES;
[self performSegueWithIdentifier:#"toLandscape" sender:self];
}
I hope I made it simple for understanding. Don't hesitate to improve my answer, we all learn in this life !

Related

Force iOS interface orientation

I can change the rotation through several tricks (e.g calling [[UIDevice currentDevice] setValue:rotation forKey:#"orientation"];, or presenting a UIViewController with the wanted rotation methods overriden, and dismissing it shortly after).
The issue is, when I disable the rotation lock on the phone, and rotate the phone to the wanted rotation, in a UIViewController where it isn't supported, but than move to a UIViewController (via a pop) where this rotation is supported, the rotation is not applied correctly.
Any ideas how to solve this issue?
I think I understand what you're asking and I've faced this problem before. I solved with putting this in the view controller that's getting popped:
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
if ([[UIDevice currentDevice] respondsToSelector:#selector(setOrientation:)]) {
[[UIDevice currentDevice]performSelector:#selector(setOrientation:) withObject:(__bridge id)((void *)UIInterfaceOrientationPortrait)];
}
}

iOS 8.3 keyboard orientation bug

I am facing a strange iOS 8.3 issue which shows a keyboard on a wrong orientation like this (the view controller is in Landscape mode, but the keyboard show up in Portrait mode):
I can trigger this issue by following these steps:
Create 2 UIViewController subClass: ViewControllerA and ViewControllerB
in ViewControllerA implement supportedInterfaceOrientations and return UIInterfaceOrientationMaskPortrait
in ViewControllerB implement supportedInterfaceOrientations and return UIInterfaceOrientationMaskLandscape
Create a UINavigationController subclass called NavigationController, implement supportedInterfaceOrientations and return [self.topViewController supportedInterfaceOrientations] (I'm doing this because I want to keep the NavigationController and it's rootVC from rotating)
Use the NavigationController as initial view controller of the app, set ViewControllerA as the NavigationController's rootViewContrller
Launch the app, ViewControllerA will shown up in Portrait. Show a button on ViewControllerA, press the button will present ViewControllerB by using presentViewController:animated:completion
ViewControllerB will show up in Landscape; Show a text field on ViewControllerB, tap on the text field will trigger the keyboard, but the keyboard is in Portrait mode, just like the image above.
PS. You can download and run the Xcode project on github
This issue seems only appears on iOS 8.3. Am I doing something wrong ? Or maybe this is just another bug of iOS ?
By the way, this issue won't happen if you just show ViewControllerA directly without a ViewController. So if this is a bug of iOS, how can I avoid subclassing UINavigationController but still keep ViewControllerA which is the rootViewController of a UINavigationController from rotating.
UPDATE: This bug still appears on iOS 8.4, I fired a bug report and got replies from apple on June 17th 2015, they said it has been addressed in the latest iOS 9 beta.
I've asked a tech support and they said
Our engineers have reviewed your request and have determined that this would be best handled as a bug report.
So seems that it's confirmed a system bug.
And my current solution is adding a ViewControllerC (support both portrait and landscape mode) between ViewControllerA and ViewControllerB: ViewControllerA should present ViewControllerC with animation, after ViewControllerC appeared, present ViewControllerB without animation.
To make the transition looks natural, you can set the same background colour as ViewControllerB to ViewControllerC, or take a screen shot of ViewControllerB and add it to ViewControllerC as a background image.
It's not a perfect solution, for users, they may find out ViewControllerB takes more time to load.
We got around this issue by tricking the device into thinking that it was rotated into a different orientation and then into its intended orientation.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
self.segmentedControl.selectedSegmentIndex = 0;
if(!self.rotatingForDismissal)
{
[self.tableNumberTextField becomeFirstResponder];
}
if(SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"8.3"))
{
[[UIDevice currentDevice] setValue:#(UIDeviceOrientationLandscapeLeft) forKey:#"orientation"];
[[UIDevice currentDevice] setValue:#(self.interfaceOrientation) forKey:#"orientation"];
}
}
I was able to make the keyboard appear correctly using the following.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if (SYSTEM_VERSION_GREATER_THAN(#"7.1") && SYSTEM_VERSION_LESS_THAN(#"9.0"))
{
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
[[UIDevice currentDevice] setValue:#(UIDeviceOrientationUnknown) forKey:#"orientation"];
[[UIDevice currentDevice] setValue:#(orientation) forKey:#"orientation"];
}
}
Note that using UIDeviceOrientationUnknown does not cause side effects such as inappropriate auto rotation of views.

willAnimateRotationToInterfaceOrientation not called on ios6/7

I have an old app, that still lives on iTunes, written in iOS 5. I would like to update it to run on ios 6 and 7. Everything has been fine so far, and I have updated my code to use ARC. However when trying to maintain the same autorotation philosophy I keep hitting a brick wall. I have already checked relative topics within SO like:
Forcing landscape and autorotate in iOS 7,
Autorotate in iOS 6 has strange behaviour
and following a similar topic I have found this:
iOS 6 Autorotation Problems and Help
which lead me to do the following:
I have set the rootViewController within my AppDelegate like so:
self.preloadingViewController = [[PreloadingViewController alloc] initWithNibName:#"PreloadingViewController" bundle:nil];
self.window.rootViewController = self.preloadingViewController;
I have placed:
- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window{
return UIInterfaceOrientationMaskAllButUpsideDown;
}
within my AppDelegate. I have overriden shouldAutorotate and supportedInterfaceOrientations within the SuperViewController (parent in inheritance terms) of all of my app's UIViewControllers (including PreloadingViewController mentioned above):
- (BOOL)shouldAutorotate{
return YES;
}
- (NSUInteger)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskAllButUpsideDown;
}
and in every child UIViewController, I override
- (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
with code to layout ui elements in the desirable manner for portrait and landscape orientations.
Finally my app's plist file under
Supported interface orientations
contains:
Portrait (bottom home button), Landscape (left home button), Landscape
(right home button)
all the orientations I want to support.
Still, even though supportedInterfaceOrientations and shouldAutorotate are being called for every orientation change on my rootViewController, willAnimateRotationToInterfaceOrientation is never being called. I have even overriden shouldAutomaticallyForwardRotationMethods in my SuperViewController to return YES, but to no avail.
What am I doing wrong here? Any ideas? I have even considered that the old ios5 - style xibs cause the issue but I do not think this is the case.
Thanks in advance.
In iOS 6 the way willAnimateRotationToInterfaceOrientation is called changed.
Use:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
// Something
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
// Something
}
in your rootController.
EDIT:
New main.m
#import <UIKit/UIKit.h>
#import "yourAppDelegate.h"
int main(int argc, char *argv[])
{
#autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([yourAppDelegate class]));
}
}
Since my rootViewController was the only one getting its shouldAutorotate and supportedInterfaceOrientations methods called, I decided to register every other view controller to get notified of any UIDeviceOrientationDidChangeNotification.
[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(orientationChanged:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
- (void)orientationChanged:(NSNotification *)notification
{
NSLog(#"Orientation changed!");
[self layoutForOrientation:[[UIDevice currentDevice] orientation]];
}
Within my layoutForOrientation: method I handled the uiview's controls orientation.
However, although I did receive UIDeviceOrientationDidChangeNotification notifications normally, my view orientation would not actually change to match the current orientation, i.e. if the new orientation was say, UIInterfaceOrientationLandscapeRight, the view orientation would remain in UIInterfaceOrientationPortrait and its children views would get rotated by 90 degrees. This did certainly not look right. After pulling a lot of hair out, I decided to abandon this route.
Instead, I have set my rootViewController to be a UINavigationController and have it push successive UIViewControllers on top of it. Since I did not want a navigation bar visible in my app I have set the navigation controller's navigationBarHidden property set to YES. This way the method willAnimateRotationToInterfaceOrientation is getting called on every UIViewController that is currently at the top of the navigationCotroller's stack of controllers, and its corresponding view and view controls rotate correctly for the desired orientations.
This works because most of my view controllers supported interface orientations match the mask: UIInterfaceOrientationMaskAllButUpsideDown, the default behaviour for iPhone. Should I needed to support different orientations, I would have to subclass the UINavigationController and override supportedInterfaceOrientations and shouldAutorotate to enable the desired orientations support for the navigation stack's top view controller, as per Apple's example project: AlternateViews.
Implement this method, return YES for what you need.
-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation

When using different view controllers (portrait and landscape) to handle orientation changes, is a segue the most appropriate way to handle this?

I have been looking into ways of setting up separate landscape and portrait view controllers to handle a changing orientation. The code posted below is from Apple stating how to do this. I noticed they use performSegueWithIdentifier. It seems odd that a segue is being used.
In order to create a segue on the storyboard I'm assuming I must create a hidden button and drag the connection from the portrait to the landscape view controller. I can then set the segue identifier to "DisplayAlternateView". What is the default segue animation? Or is the default to turn the animation off?
Also why is this code in the awakeFromNib method? Shouldn't it be in viewDidLoad? Is awakeFromNib called before viewDidLoad?
Also I'm assuming I must have a different target action for every scene of my storyboard. If I have portrait view A, B and C with a corresponding landscape view A, B and C, should I have the following changes to the Apple code
on my A view:
selector:#selector(orientationChangedA:)
then on my B
selector:#selector(orientationChangedB:)
then on my C
selector:#selector(orientationChangedC:)
This way each method can perform it's own segue.
I feel like I might be over complicating things here. Are the separate segues causing me to do extra work or is this how how orientation switching to separate view controllers normally handled?
Here is the code from Apple saying how to handle orientation changes with different view controllers:
#implementation PortraitViewController
- (void)awakeFromNib
{
isShowingLandscapeView = NO;
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(orientationChanged:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
}
- (void)orientationChanged:(NSNotification *)notification
{
UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
if (UIDeviceOrientationIsLandscape(deviceOrientation) &&
!isShowingLandscapeView)
{
[self performSegueWithIdentifier:#"DisplayAlternateView" sender:self];
isShowingLandscapeView = YES;
}
else if (UIDeviceOrientationIsPortrait(deviceOrientation) &&
isShowingLandscapeView)
{
[self dismissViewControllerAnimated:YES completion:nil];
isShowingLandscapeView = NO;
}
}

Modal view controller not presented anymore after being presented 2/3 times

Here is the thing, I have to integrate an augmented reality functionality into an app. After testing it aside, now I'm ready to integrate it. The app always runs in Portrait, I decided to show the augmented reality when rotating the iPhone in Landscape Right or Left (and of course, if I go back in Portrait the original view controller is shown). In fact the augmented reality is presented modally.
I've got viewController calling ARViewController (modally). It works fine if I rotate like 2 or 3 times, but then this ARViewController is not called anymore, but the app is still running, no crash, no freeze. This ARViewController is initialised with ARController, a class computing all needed for the augmented reality. If I call this ARViewcontroller without the ARController, switching between the view controllers works very fine, a blank window will be called but at least I can rotate the device as much as I want. So, this must come from the ARController, I documented myself on memory leaks (by the way I'm using ARC), I think this could be the reason to the issue since I'm using this ARController many times.
But before going further, I'd like to know if I'm doing anything wrong that could influence the ARController by switching between view controllers:
Here is how I call the ARViewController in viewController:
if (orientation == UIDeviceOrientationLandscapeLeft) {
NSLog(#"ViewController Landscape left");
ARViewController *arVC = [[ARViewController alloc] initWithNibName:#"ARViewController" bundle:nil];
[self setCameraViewController:arVC];
[arVC setModalTransitionStyle: UIModalTransitionStyleFlipHorizontal];
[[self navigationController] presentModalViewController:cameraViewController animated:YES];
arVC = nil;
}
else if (orientation == UIDeviceOrientationLandscapeRight) {
NSLog(#"ViewController Landscape Right");
ARViewController *arVC = [[ARViewController alloc] initWithNibName:#"ARViewController" bundle:nil];
[self setCameraViewController:arVC];
[arVC setModalTransitionStyle: UIModalTransitionStyleFlipHorizontal];
[[self navigationController] presentModalViewController:cameraViewController animated:YES];
arVC = nil;
}
the initialisation of ARViewController:
- (void)viewDidLoad {
[super viewDidLoad];
self.arController = [[ARController alloc] initWithViewController:self];
arController.filterDiscover = filterDiscover;
arController.filterEat = filterEat;
arController.filterSleep = filterSleep;
// Listen for changes in device orientation
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(deviceOrientationDidChange:) name: UIDeviceOrientationDidChangeNotification object:nil];
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
//if ViewController presents this modal ARViewController
if(!arController.filterDiscover && !arController.filterEat && !arController.filterSleep)
[arController callAlertViewToFilter];
else
[arController loadData];
}
And finally here is how I go back to the original view controller if I rotate to Portrait in ARViewController:
if (orientation == UIDeviceOrientationPortrait){
NSLog(#"ARViewController Portrait");
[self setArController:nil];
[[super presentingViewController] dismissModalViewControllerAnimated:YES];
}
I tried to be as clear as possible, if anyone has ever had an issue similar to this, it could be great have some clues to solve this. I could have shown the code of the ARController but it is a little too long, for now I'd just like to know if there is anything wrong here. If needed I'll show it.
This might help, I found this output in the debug area when the ARViewController is not being displayed anymore:
2012-10-24 17:57:51.072 beiceland[20348:907] ViewController Landscape Right
2012-10-24 17:57:51.073 beiceland[20348:907] Warning: Attempt to present <ARViewController: 0x203f0c60> on <RevealController: 0x1cd5dca0> while a presentation is in progress!
My bad I was way out of the solution here.
I used the debugger and breakpoints and repeated the critical sequence, I found out I was not entering:
- (void)deviceOrientationDidChange:(NSNotification *)notification
anymore. So this is the first time I see such things, the listener for device orientation changes suddenly stops firing. Consequently my solution is quite brutal but at least it has the merit of stopping the issue, just after detecting the device orientation change I stop the listener:
if (orientation == UIDeviceOrientationLandscapeLeft) {
NSLog(#"ViewController Landscape left");
[[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] removeObserver:self];
ARViewController *arVC = [[ARViewController alloc] initWithNibName:#"ARViewController" bundle:nil];
[self setCameraViewController:arVC];
arVC = nil;
[cameraViewController setModalTransitionStyle: UIModalTransitionStyleFlipHorizontal];
[[super navigationController] presentModalViewController:cameraViewController animated:YES];
}
And each time I'm using the viewController (the calling view controller), I reinitialize the listener, it means in viewDidAppear:
// Listen for changes in device orientation
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(deviceOrientationDidChange:) name: UIDeviceOrientationDidChangeNotification object:nil];
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
Well now it's solved but I have to admit I'm disappointed of finding such kind of solution.
Well, I never used ARC before, but according to what I see, you initialize a ARViewController everytime you change orientation ( on landscape ). Why not instantiating 2 ARViewController for good, and calling them everytime ?
Except if you are sure that these are released everytime you switch to portrait.
Plus, why don't you just use pushViewController:animated: ?
And one last thing, you presentModalViewController to you navigationController, but you dismiss it in [super presentingViewController], maybe you could add this one ?

Resources