How to switch to Landscape View properly? - objective-c

I have a table view with some words, and i present flash-card style landscape view when the device rotates. I made it by observing the "UIDeviceOrientationDidChangeNotification".
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(openLandscapeMode) name:#"UIDeviceOrientationDidChangeNotification" object:nil];
1)That works fine and smooth, but the problem is that when the we are in the landscape, i don't want the viewcontroller to react to the spinning around the vertical axis,so that i could lay the phone on the table and it would still be in the landscape.
Maybe i should somehow observe the horizontall spinnings, instead of deviceorientation?
-(void)openLandscapeMode
{
if([[UIDevice currentDevice]orientation]==UIDeviceOrientationLandscapeLeft||[[UIDevice currentDevice]orientation]==UIDeviceOrientationLandscapeRight)
{
LandscapeCardViewController *landscape = [[LandscapeCardViewController alloc]init];
landscape.words = words;
landscape.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:landscape animated:YES];
NSLog(#"Switch to %#",[[NSUserDefaults standardUserDefaults]valueForKey:#"ChosenWordInCard"]);
[landscape release];
}
else
{
[self dismissModalViewControllerAnimated:YES];
[[UIApplication sharedApplication]setStatusBarOrientation:UIInterfaceOrientationPortrait];
}
}
2)The second question is where to remove observer, if this controller is in a tab bar, and i want to perform the same transition in another controller in the same tabbar,but,of course,with another landscape view?
I tried in viewWillDissappear, but it doesn't work properly.
Thanks a lot!

For your first question, there should be a method in your viewcontroller which you may need to edit to only support portrait
-(BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)interfaceOrientation {
return UIInterfaceOrientationPortrait(interfaceOrientation); //only allow portrait
}
That will stop it auto rotating to landscape, while keeping your original method intact
For the second. What about when the transition is complete? Then re-add it when the view appears again. And then in your landscape controller, add it to re-detect when the device is portrait.

I found the solution
I changed else to if([[UIDevice currentDevice]orientation]==UIDeviceOrientationPortrait||[[UIDevice currentDevice]orientation]==UIDeviceOrientationPortraitUpsideDown) and everything works fine!
Strange, but it works!
About removing the observer - i do it in -viewWillAppear,checking,if i am not in landscape now.

Related

Application tried to present modally an active controller : UIImagePickerController

I'm struggle at this for 2 days and believe that this is the moment I should call for help. After I search SOF for a while, none of any answer could solve my problem. Here are my application ...
In the application,
Device is iPad, iOS 6
RootViewController is NavigationController
TopViewController is TabBarController
In this TabBarController, I present a popoverController from right bar button of navigation bar
In presenting popover there is a button to allow user to pick image from by taking new one or pick from existing.
To pick new one, I presentViewController UIImagePickerController to allow user to take photo with divice camera. presentModalViewController:animated: if iOS < 6, and presentViewController:animated:completion: for iOS > 6
I also hide Status Bar before presentation
To select from existing photo, I do presentPopoverFromBarButtonItem:permitArrowDirections:animated:
PopoverViewController also referencing by A TabBarController
Here is the issue
Present UIImagePickerController will always failed if user try to pick new one first with exception "Application tried to present modally an active controller <[name of view controller that try to present]>"
BUT, if user try to pick image from camera roll for once and then try to take new one again, it won't fail.
Here are what I tried
present from RootViewController
present from TopViewController (TabBarController)
present from popoverViewController itself
present from a tab of TabBarController
hide popoverViewController before presentation
resignFirstResponder from a textField in popoverViewController
Here is the current code I'm using
// PopoverViewController, presented by a tab in TabBarController
- (IBAction)takePhoto:(id)sender {
[self.delegate takePhotoWithDeviceCamera];
}
// A Tab in TabBarController, delegate of popoverViewController
- (void)takePhotoWithCamera {
[[UIApplication sharedApplication] setStatusBarHidden:YES];
if ([UIDevice OSVersion] < 6.0) {
[self presentModalViewController:cameraPicker animated:YES];
} else {
[self presentViewController:cameraPicker animated:YES completion:nil];
}
}
Any idea what would cause this error? Any suggestion are welcome. Thank you.
Got the same trouble than you and finally got the solution based on #CainaSouza's answer. I've been working with Xamarin.iOS so I'll make my answer in C#, but it can be easily translated to Objective-C.
I'm using the same code as #CainaSouza to call the controller:
UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController (customController, true, null);
And then I add the following code to my custom RootViewController:
public override void PresentViewController (UIViewController viewControllerToPresent, bool animated, Action completionHandler)
{
if (PresentedViewController != viewControllerToPresent) {
base.PresentViewController (viewControllerToPresent, animated, completionHandler);
}
}
The trick is to check if you haven't presented that UIViewController before.
I know it's an old question, but hope it will help someone. :)
Present the imagePicker controller in a popoverController(in case of iPad). This will not give you that error.
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
UIPopoverController *popover = [[UIPopoverController alloc] initWithContentViewController:picker];
[popover presentPopoverFromRect:self.selectedImageView.bounds inView:self.selectedImageView permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
self.popOver = popover;
}
else {
[self presentModalViewController:picker animated:YES];
}
Best Regards.
Have you tried to present it like this?
[self.view.window.rootViewController presentModalViewController:cameraPicker animated:YES];
My guess is that the cameraPicker instance is not correctly allocated/released. Try creating the cameraPicker inside your - (void)takePhotoWithCamera method rather than relying on a previously created instance. You'll get a handle to the picker instance in the callback methods...
I had the same problem - I wanted users to take photos using a full screen view (i.e. call presentViewController and pass UIImagePickerController controller instance) and select existing photos from a popover (I associated it with a popover using initWithContentViewController). I reused the same instance of UIImagePickerController for both camera and popover and it threw the same exception if I tried to run a camera before opening a popover.
I turned out to cause a problem and my solution was simply to have two instances of UIImagePickerController - one for camera (which I presented from a main view) and another one for popover. It works so far. :-)
Not sure if it is still actual for the original poster, but hopefully it will help anyone else who encounter this discussion.

launching a modal view controller to work around the orientation "lock" of tab bar and nav VCs?

I have an iPhone app with a root view controller (VC) of UITabBarController (set to portrait orientation) with several tabs, one of which is a simple UIViewController. In that UIViewController is a single button - "Play Video", which, when clicked opens a modal view of the video (and automatically starts playing the video). The video view is a UIWebView in a UIViewController. I've been trying to get the Web View's VC to change orientation to landscape but have not had any luck.
I've looked around and understand that if you have a Tab Bar or a Nav controller, all children VCs will be the same orientation as the parent - makes sense. This is why I made the web view's VC modal, hoping this is a way around the orientation issue.
My question is: is this accurate - that using modal will not require the web view VC to be portrait and can respond to the shouldAutorotateToInterfaceOrientation method (even though I have not yet been able to get it to work)?
BTW, using iOS 6.
Thanks in advance.
Apparently in ios6 and above, the way rotation works is different. So what you have to do is the following
In your .plist support all 4 orientations.
Subclass the UITabBarController (for e.g: CustomTabBarController)
In the CustomTabBarController put the following lines of code
-(NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
return UIInterfaceOrientationPortrait;
}
In your app delegate or where ever you are initializing UITabBarController, replace those instances with CustomTabBarController instances.
In your modal controller put the lines
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
return UIInterfaceOrientationLandscapeLeft;
}
-(BOOL)shouldAutorotate{
return NO;
}
And it should all work.
Apparently the trick, I found is that, UITabBarController will not listen to your instructions. It will support all the orientations you mention in the .plist.
There fore you have to subclass it.
I tried doing all of the above and it works fine. Do let me know and I can send you the code if you want.
Try this. Just have portrait set in the summary screen, then in the app delegate, implement this:
- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
return UIInterfaceOrientationMaskAll;
}
In the tab bar controller (and no other rotation code):
-(BOOL)shouldAutorotate {
return NO;
}
And finally, in the modal view controller:
-(BOOL)shouldAutorotate {
return YES;
}
- (NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskAll;
}

modalViewController presented from UISplitViewController comes up as the wrong orientation

I have a UISplitViewController that is set at the rootView of my application. When viewDidLoad is called in my left view controller I do a check, then present a modal view controller using the following:
SiteConfiguration *config = [[SiteConfiguration alloc] initWithStyle:UITableViewStyleGrouped];
config.firstLoad = YES;
UINavigationController *configNav = [[UINavigationController alloc] initWithRootViewController:config];
if ([Utility isIpad]) {
configNav.modalPresentationStyle = UIModalPresentationFormSheet;
configNav.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[[AppDelegate instance].splitViewController presentModalViewController:configNav animated:YES];
} else {
[self presentModalViewController:configNav animated:YES];
}
If the iPad is in landscape mode while the app loads, the modalView is shown with an incorrect orientation:
I can rotate the iPad to fix this, but WHY does it load up wrong? I have shouldAutorotateToInterfaceOrientation: returning YES in my SiteConfiguration viewController. What could be causing this?
Be careful of where you choose to present your modal controller.
I've had experience with some custom modal controllers and setting the orientation of the modal controller (and its shadows!) in
- (void)viewDidLoad:(BOOL)animated
didn't always behave as expected.
Put your code (presentModalViewController:configNav animated:YES) in
- (void)viewDidAppear:(BOOL)animated
instead. (Do this as well with any code that sets a subviews frame or does any manipulation of layers, e.g. the shadow layer and shadow properties).
As far as I can tell, the rotation may not be apparent to subviews of the rotated view until after - (void)viewDidLoad:(BOOL)animated due to threading issues (one thread may start drawing your subview or modal controller's view before rotation is passed down to the subviews (and modal controllers) by the main thread). Someone with more experience with threads than myself might be able to shed more light on this.
shouldAutorotateToInterfaceOrientation: doesn't actually rotate the interface, the app does that upon receiving a UIDeviceOrientationDidChangeNotification notification.
Try adding a check for the device orientation in the -(void) viewDidAppear:(BOOL)animated method.
To force an interface rotation, use the following piece of code.
UIDeviceOrientation toInterfaceOrientation = [[UIDevice currentDevice] orientation];
[UIApplication sharedApplication].statusBarOrientation = toInterfaceOrientation;

How to handle autorotation in AVCaptureVideoPreviewLayer?

My application supports all orientations except PortraitUpsideDown.
In my view hierarchy I have an AVCaptureVideoPreviewLayer as a sublayer in the top view which is UIImageView. Then below it in view hierarchy are several overlay views showing controls.
Overlay views are working properly with orientation changes, but I don't how to be with this AVCaptureVideoPreviewLayer. I want it to behave like in Camera app, so that previewLayer stays still and controls are smoothly reorganized. Right now since the main view is rotated on orientation change, my preview layer is also rotated, which means that in landscape view it stays in portrait view, taking only part of the screen and the picture from camera being also rotated by 90 degrees. I've managed to rotate the preview layer manually, but then it has this orientation change animation, which leads to the background being seen for a while during the animation.
So what is the proper way to autorotate the controls while making previewLayer stay still?
In my implementation I have subclassed the UIView for my view which I want to rotate and something like viewController for this view which is just a subclass of NSObject.
In this viewController I do all the the checks related to changes of orientation, make decision if I should change orientation of my target view, and if yes, then I call method of my view for changing its orientation.
First of all we need to fix the orientation of whole application interface to Portrait mode, so that our ACCaptureVideoPreviewLayer always stays still.
This is done in the MainViewController.h:
(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation`
{
return interfaceOrientation==UIInterfaceOrientationPortrait;
}
It returns NO to all orientations except Portrait.
In order to our custom viewController be able to track the changes of device orientation we need to make it an observer of corresponding notifications:
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(orientationChanged) name:UIDeviceOrientationDidChangeNotification object:nil];
I put these lines in the (void)awakeFromNib method of my viewController.
So each time the device orientation is changed, the viewController's method orientationChanged will be called.
Its purpose is to check what is the new orientation of device, what was the last orientation of device and decide if to change it. Here is the implementation:
if(UIInterfaceOrientationPortraitUpsideDown==[UIDevice currentDevice].orientation ||
lastOrientation==(UIInterfaceOrientation)[UIDevice currentDevice].orientation)
return;
[[UIApplication sharedApplication]setStatusBarOrientation:[UIDevice currentDevice].orientation animated:NO];
lastOrientation=[UIApplication sharedApplication].statusBarOrientation;
[resultView orientationChanged];
If the orientation is the same as before or in PortraitUpsideDown then do nothing.
Else it sets the status bar orientation to the proper one, so that when there is an incoming call or ossification, it will appear on the proper side of the screen. And then I call also method in the target view where all the corresponding changes for new orientation are done, like rotating, resizing, moving the other elements of interface in this view corresponding to the new orientation.
Here is the implementation of the orientationChanged in target view:
Float32 angle=0.f;
UIInterfaceOrientation orientation=[UIApplication sharedApplication].statusBarOrientation;
switch (orientation) {
case UIInterfaceOrientationLandscapeLeft:
angle=-90.f*M_PI/180.f;
break;
case UIInterfaceOrientationLandscapeRight:
angle=90.f*M_PI/180.f;
break;
default: angle=0.f;
break;
}
if(angle==0 && CGAffineTransformIsIdentity(self.transform)) return;
CGAffineTransform transform=CGAffineTransformMakeRotation(angle);
[UIView beginAnimations:#"rotateView" context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
[UIView setAnimationDuration:0.35f];
self.transform=transform;
[UIView commitAnimations];
Of course here you can add any other changes like translation, scaling of different views of your interface that need to respond to new orientation and animate them.
Also you may not need the viewController for this, but do all just in the class of your view.
Hope that the general idea is clear.
Also don't forget to stop getting notification for orientation changes when you don't need them like:
[[NSNotificationCenter defaultCenter]removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
[[UIDevice currentDevice]endGeneratingDeviceOrientationNotifications];
iOS 8 solution:
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
if ([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationLandscapeLeft) {
self.layer.connection.videoOrientation = AVCaptureVideoOrientationLandscapeLeft;
} else {
self.layer.connection.videoOrientation = AVCaptureVideoOrientationLandscapeRight;
}
}
in your setup code:
self.layer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session];
self.layer.videoGravity = AVLayerVideoGravityResizeAspectFill;
if ([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationLandscapeLeft) {
self.layer.connection.videoOrientation = AVCaptureVideoOrientationLandscapeLeft;
} else {
self.layer.connection.videoOrientation = AVCaptureVideoOrientationLandscapeRight;
}
THe main problem is that when I get the notification, the statusbar has not yet rotated, so checking the current value give in fact the value before the rotation. So I added a little delay (here 2 seconds) before calling the method that check the statusbarorientation and rotates my subview :
-(void) handleNotification:(NSNotification *) notification
{
(void) [NSTimer scheduledTimerWithTimeInterval:(2.0)
target:self
selector:#selector(orientationChanged)
userInfo:nil
repeats:NO ] ;
}
The rest of the code is the one from BartoNaz.

Rotation Portrait Landscape with 2 XIB

i have got 2 GUIs and 2 Controllers
1 is called landscapeguicontroller and the second is called highguicontroller.
Now generally i call the highguicontroller, and when i rotate my iphone it detects that and then it shows the landscapeguicontroller:
Code:
landscapeguicontroller *neu =[[landscapeguicontroller alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:neu animated:YES];
[self dismissModalViewControllerAnimated:YES];
The Problem is that then the animation pushes the new window from the beyond side of the iphone up into the window.
In the Landscapeguicontroller,i have added to the the following lines:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
when i want go back to the highguicontroller i call:
[self dismissModalViewControllerAnimated:YES];
that all works , but just in the second animation i see the correct "rotation animation".
Have you got any suggestions?
So a short Problem description:
in the 1. animation from high to landscape, the landscape is pushed into the window
BUT in the 2. animation from landscape to high, the rotation looks like a real rotation...
i want the 1.animation look like the 2. animation
best regards
Ploetzeneder
To avoid "The Problem is that then the animation pushes the new window from the beyond side of the iphone up into the window.", try setting the view controller's modalTransitionStyle property to one of the following, whatever you prefer:
typedef enum {
UIModalTransitionStyleCoverVertical = 0,
UIModalTransitionStyleFlipHorizontal,
UIModalTransitionStyleCrossDissolve,
} UIModalTransitionStyle;
Also, if you want to avoid the animated rotation, you can set your shouldRotate... method to disallow other orientations, but then set up to receive notifications when the device physically changes orientations, and present your modal viewcontroller when in the appropriate orientation for it. See Apple's "AlternateViews" sample code for an example of this.
The notifications reflect the physical orientation of the device, and you can receive them whether the interface is allowed to change or not. (You can look at the UIApplications's statusBarOrientation property to see what orientation the UI is in).
It sounds like you want the sequence to go like this:
Physically rotate the device from portrait to landscape
Animate the portrait view (highguicontroller) to landscape
Push the landscape view (landscapeguicontroller) up from the new "bottom" of the screen
If that's right, you'll need to have something like the following in your highguicontroller implementation:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown;
}
This will take care of step 2 (it will rotate the portrait view to landscape in either direction).
Then you'll want something like this:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
if(fromInterfaceOrientation == UIInterfaceOrientationPortrait) {
[self presentModalViewController:landscapeguicontroller animated:YES];
}
else {
[self dismissModalViewControllerAnimated:YES];
}
}
That should present the landscape view after the rotation animation is complete and then dismiss it after the device is rotated back to portrait.
Hope that helps!