just updated to IOS SDK 6 and now I'm confused. Tried to find a solution but I failed.
Created a new "Single View iPad APP" (I have an iPad 3) in xcode
Disabled Portrait & Upside-Down interface orientations in TARGETS->Summary
Set simulated metrics "orientation" to "Landscape"
Added following lines of code into the view controller file:
- (BOOL)shouldAutorotate {
return YES;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return UIInterfaceOrientationLandscapeLeft | UIInterfaceOrientationLandscapeRight;
}
- (NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskLandscape;
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(#"%#", NSStringFromCGRect(self.view.frame));
}
Run APP
My output is: 2012-10-09 18:18:40.149 TestApp[6165:907] {{20, 0}, {748, 1024}}
Does anybody know why the frame is not ... {{0, 20}, {1024, 748}} as I would expect? Maybe I missed something!
Thank you!
I don't think you missed anything, but if you wrap your view controller with a UINavigationController, the frame will be set as expected.
NB: With the iOS 6 SDK, you can no longer combine any of the UIInterfaceOrientation constants as a mask. So you need:
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return UIInterfaceOrientationLandscapeLeft;
}
That doesn't explain the issue, though, since preferredInterfaceOrientationForPresentation is not called when a view controller is the root controller of a UIWindow.
Related
I want to do some animation along with device rotation. On iOS 8, I can do this:
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
// My custom animation
} completion:nil];
}
But this method is not available on iOS 7. I tried to do the similar thing as follows:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
[self.transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
// Custom animations
} completion:nil];
}
But the self.transitionCoordinator object is nil here. Does anyone know how to achieve the same effect on iOS 7?
I'm not sure if I just call this animateAlongsideTransition method in the wrong place or even if animateAlongsideTransition is the right method to call. So any help/guidance would be appreciated.
Thanks
It looks like I have found an answer for my own question. For those who are also wondering the same thing, willAnimateRotationToInterfaceOrientation method is the one to use.
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
[UIView animateWithDuration:duration animations:^{
// Custom animations
}];
}
And it looks like the custom animation has to be here. If I move the animation block to the willRotateToInterfaceOrientation method, it won't work.
I'm using the new adaptive "Present As Popover" capability of iOS 8. I wired up a simple segue in the StoryBoard to do the presentation. It works great on an iPhone 6 Plus as it presents the view as a popover and on an iPhone 4s it shows as a full screen view (sheet style).
The problem is when shown as a full screen view, I need to add a "Done" button to the view so dismissViewControllerAnimated can be called. And I don't want to show the "done" button when it's shown as a popover.
I tried looking at the properties of both presentationController and popoverPresentationController, and I can find nothing that tells me if it is actually being shown as a popover.
NSLog( #"View loaded %lx", (long)self.presentationController.adaptivePresentationStyle ); // UIModalPresentationFullScreen
NSLog( #"View loaded %lx", (long)self.presentationController.presentationStyle ); // UIModalPresentationPopover
NSLog( #"View loaded %lx", (long)self.popoverPresentationController.adaptivePresentationStyle ); // UIModalPresentationFullScreen
NSLog( #"View loaded %lx", (long)self.popoverPresentationController.presentationStyle ); // UIModalPresentationPopover
adaptivePresentationStyle always returns UIModalPresentationFullScreen and presentationStyle always returns UIModalPresentationPopover
When looking at the UITraitCollection I did find a trait called "_UITraitNameInteractionModel" which was only set to 1 when it was actually displayed as a Popover. However, Apple doesn't provide direct access to that trait through the traitCollection of popoverPresentationController.
The best way (least smelly) I've found to do this is to use the UIPopoverPresentationControllerDelegate.
• Ensure the presented view controller is set as the UIPopoverPresentationControllerDelegate on the UIPopoverPresentationController being used to manage the presentation. I'm using a Storyboard so set this in prepareForSegue:
segue.destinationViewController.popoverPresentationController.delegate = presentedVC;
• Create a property in the presented view controller to keep track of this state:
#property (nonatomic, assign) BOOL amDisplayedInAPopover;
• And add the following delegate method (or add to your existing delegate method):
- (void)prepareForPopoverPresentation:(UIPopoverPresentationController *)popoverPresentationController
{
// This method is only called if we are presented in a popover
self.amDisplayedInAPopover = YES;
}
• And then finally in viewWillAppear: - viewDidLoad: is too early, the delegate prepare method is called between viewDidLoad: and viewWillAppear:
if (self.amDisplayedInAPopover) {
// Hide the offending buttons in whatever manner you do so
self.navigationItem.leftBarButtonItem = nil;
}
Edit: Simpler method!
Just set the delegate (making sure your presentedVC adopts the UIPopoverPresentationControllerDelegate):
segue.destinationViewController.popoverPresentationController.delegate = presentedVC;
And supply the method:
- (void)prepareForPopoverPresentation:(UIPopoverPresentationController *)popoverPresentationController
{
// This method is only called if we are presented in a popover
// Hide the offending buttons in whatever manner you do so
self.navigationItem.leftBarButtonItem = nil;
}
I check to see if the popoverPresentationController's arrowDirection is set after the view is laid out. For my purposes, this works well enough and covers the case of popovers on smaller screened devices.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if (popoverPresentationController?.arrowDirection != UIPopoverArrowDirection.Unknown) {
// This view controller is running in a popover
NSLog("I'm running in a Popover")
}
}
How about
if (self.modalPresentationStyle == UIModalPresentationPopover)
It's working for me
The official way to implement this is first remove the Done button from your view controller and second, when adapting to compact embed your view controller in a navigation controller, adding the done button as a navigation item:
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
return UIModalPresentationStyle.FullScreen
}
func presentationController(controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
let navigationController = UINavigationController(rootViewController: controller.presentedViewController)
let btnDone = UIBarButtonItem(title: "Done", style: .Done, target: self, action: "dismiss")
navigationController.topViewController.navigationItem.rightBarButtonItem = btnDone
return navigationController
}
func dismiss() {
self.dismissViewControllerAnimated(true, completion: nil)
}
Full Tutorial
I tested all solutions presented in this post. Sorry, none works correctly in all cases. For example in iPad split view presentation style can change while dragging split view line, so we need specific notification for that.
After few hours of researches i found solution in apple sample (swift):
https://developer.apple.com/library/ios/samplecode/AdaptivePhotos/Introduction/Intro.html#//apple_ref/doc/uid/TP40014636
Here is the same solution in obj-c.
First in prepareForSegue function set the popoverPresentationController delegate. It can be also set in MyViewController "init", but not in "viewDidLoad" (because first willPresentWithAdaptiveStyle is called before viewDidLoad).
MyViewController *controller = [segue destinationViewController];
controller.popoverPresentationController.delegate = (MyViewController *)controller;
Now MyViewController object will receive this notification every time iOS changes presentation style, including first presenting. Here is example implementation which shows/hides "Close" button in navigationController:
- (void)presentationController:(UIPresentationController *)presentationController
willPresentWithAdaptiveStyle:(UIModalPresentationStyle)style
transitionCoordinator:(nullable id<UIViewControllerTransitionCoordinator>)transitionCoordinator {
if (style == UIModalPresentationNone) {
// style set in storyboard not changed (popover), hide close button
self.topViewController.navigationItem.leftBarButtonItem = nil;
} else {
// style changed by iOS (to fullscreen or page sheet), show close button
UIBarButtonItem *closeButton =
[[UIBarButtonItem alloc] initWithTitle:#"Close" style:UIBarButtonItemStylePlain target:self action:#selector(closeAction)];
self.topViewController.navigationItem.leftBarButtonItem = closeButton;
}
}
- (void)closeAction {
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
The UIPresentationController which manages your view controller is presenting it by setting the modalPresentationStyle to UIModalPresentationPopover.
As per UIViewController reference:
presentingViewController
The view controller that presented this view
controller. (read-only)
modalPresentationStyle
UIModalPresentationPopover: In a horizontally regular environment, a presentation style where the content is displayed in a popover view. The background content is dimmed and taps
outside the popover cause the popover to be dismissed. If you do not
want taps to dismiss the popover, you can assign one or more views to
the passthroughViews property of the associated
UIPopoverPresentationController object, which you can get from the
popoverPresentationController property.
We can therefore determine whether your view controller is inside a popover or presented modally by checking the horizontalSizeClass as follows (I assumed your button is a UIBarButtonItem)
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (self.presentingViewController.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassRegular)
self.navigationItem.leftBarButtonItem = nil; // remove the button
}
The safest place to check this is in viewWillAppear: as otherwise the presentingViewController may be nil.
Solution that works with multitasking
Assign the presenting controller as the popover's delegate
...
controller.popoverPresentationController.delegate = controller;
[self presentViewController:controller animated:YES completion:nil];
Then, in the controller, implement the delegate methods:
- (void)presentationController:(UIPresentationController *)presentationController willPresentWithAdaptiveStyle:(UIModalPresentationStyle)style transitionCoordinator:(id<UIViewControllerTransitionCoordinator>)transitionCoordinator
{
if (style != UIModalPresentationNone)
{
// Exited popover mode
self.navigationItem.leftBarButtonItem = button;
}
}
- (void)prepareForPopoverPresentation:(UIPopoverPresentationController *)popoverPresentationController
{
// Entered popover mode
self.navigationItem.leftBarButtonItem = nil;
}
My tricky solution, works perfectly.
In the PopoverViewController's viewDidLoad.
if (self.view.superview!.bounds != UIScreen.main.bounds) {
print("This is a popover!")
}
The idea is simple, A Popover's view size is never equal to the device screen size unless it's not a Popover.
I am using Xcode and after updating to iOS 8, the shouldAutorotate function doesn't work.
I don't want my viewcontroller to autorotate.
How can I restrict autorotation in iOS 8 from Xcode?
Follow these steps:
First, in info.plist select the orientations which your application supports. Meaning if your application only displays in portrait mode then select portrait as your only setting.
If you want to restrict this orientation from appdelegate you can add this code
- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
NSUInteger orientations;
UIViewController* presented = [[[[NavigationManager sharedManager ] navigationController] viewControllers] lastObject];
orientations = [presented supportedInterfaceOrientations];
return orientations;
}
Make common class for UIViewcontroller and add these methods:
BaseViewController.h
#interface BaseViewController : UIViewController{
}
BaseViewController.m
- (BOOL)shouldAutorotate
{
return NO;
}
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
Add the following code in Navigation controller class
-(BOOL)shouldAutorotate
{
return NO;
}
-(NSUInteger)supportedInterfaceOrientation
{
return UIInterfaceOrientationPortrait;
}
When updating my app to iOS6 standard the portrait / landscape is gone. Ir worked perfectly when I was building with Xcode 3. But now using latest Xcode and latest SDK the rotation is gone and it is always in portrait mode. No matter what I put in "Supported interface Orientations". And the code I used to get rotation before seems to have no effect at all.
I had these lines.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
switch (toInterfaceOrientation) {
case UIInterfaceOrientationPortrait:
case UIInterfaceOrientationLandscapeLeft:
case UIInterfaceOrientationLandscapeRight:
return YES;
default:
return NO;
}
}
How do I change and what do I change to get it work again?
First of all, in AppDelegate, write this. THIS IS VERY IMP
- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
return (UIInterfaceOrientationMaskAll);
}
Then, For UIViewControllers, in which you need only PORTRAIT mode, write these functions
- (BOOL)shouldAutorotate
{
return YES;
}
- (NSUInteger)supportedInterfaceOrientations
{
return (UIInterfaceOrientationMaskPortrait);
}
For UIViewControllers, which require LANDSCAPE too, change masking to All.
- (NSUInteger)supportedInterfaceOrientations
{
return (UIInterfaceOrientationMaskAllButUpsideDown);
//OR return (UIInterfaceOrientationMaskAll);
}
Now, if you want to do some changes when Orientation changes, then use this function.
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
}
EDIT :
A lot depends on with which controller is your UIViewController embedded in.
Eg, If its inside UINavigationController, then you might need to subclass that UINavigationController to override orientation methods like this.
subclassed UINavigationController (the top viewcontroller of the hierarchy will take control of the orientation.) did set it as self.window.rootViewController.
- (BOOL)shouldAutorotate
{
return self.topViewController.shouldAutorotate;
}
- (NSUInteger)supportedInterfaceOrientations
{
return self.topViewController.supportedInterfaceOrientations;
}
From iOS 6, it is given that UINavigationController won't ask its UIVIewControllers for orientation support. Hence we would need to subclass it.
How to support one or more landscape controllers in app that is portrait mainly in ios6:
1) in AppDelegate
- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
UINavigationController* ns = (UINavigationController*)self.window.rootViewController;
if (ns) {
UIViewController* vc = [ns visibleViewController];
//by this UIViewController that needs landscape is identified
if ([vc respondsToSelector:#selector(needIos6Landscape)])
return [vc supportedInterfaceOrientations];
}
return UIInterfaceOrientationMaskPortrait; //return default value
}
2) in UIView controller(s) that needs landscape (or portrait+lanscape etc):
//flag method
-(void)needIos6Landscape {
}
- (BOOL)shouldAutorotate
{
return YES;
}
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskAllButUpsideDown;
}
3) in controllers, to which you can RETURN from controllers, that can be rotated in landscape - this is important, otherwise they remaind landscape on return from landscape-enabled VC.
- (BOOL)shouldAutorotate
{
return YES;
}
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
4) (maybe not needed, but for sure..) - subclass navigation controller(s) you using, and add:
- (BOOL)shouldAutorotate
{
return YES;
}
- (NSUInteger)supportedInterfaceOrientations
{
UIViewController* vc = [self visibleViewController];
if (vc) {
if ([vc respondsToSelector:#selector(needIos6Landscape)]) {
return [vc supportedInterfaceOrientations];
}
}
return UIInterfaceOrientationMaskPortrait;
}
The important step is to ask for orientation only controllers from your app, because during transition between controllers, for some time there is some system controller as root, and will return incorrect value (this took me 2 hrs to find out, it was reason it was not working).
Don't know whether your issue was alike but with me, the status bar was oriented correctly (landscape) and the UIViewController was portrayed.
I changed following line in the application delegate application:didFinishLaunchingWithOptions:
//[window addSubview:navigationController.view];
self.window.rootViewController = navigationController;
Apple=> this costed me a day and a half to find out, and a lot of money!!!
I've been having a huge problem with this.
Xcode just simply turned my landscape mode app into a portrait view and It doesn't go back !
I've programmed the entire app almost to run in landscape mode in the Ipad.
On the Storyboard, every window is in lanscape.
I believe i did the settings correctly according to the images below
and finally on my viewController I have:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES;
}
Anybody know what could be going wrong or why would Xcode just set the screen to portrait out of nowhere while I was just adjusting a viewController?
If you want to only support landscape then change shouldAutorotateToInterfaceOrientation to:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
if (interfaceOrientation == UIInterfaceOrientationLandscapeLeft ||
interfaceOrientation == UIInterfaceOrientationLandscapeRight) {
return YES;
}
// Default
return NO;
}