6

In my universal app I currently override supportedInterfaceOrientations in the window's root view controller to define the orientations that are allowed. Up until now the decision was based on the device's user interface idiom:

- (NSUInteger) supportedInterfaceOrientations
{
  if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
    return (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown);
  else
    return UIInterfaceOrientationMaskAll;
}

Now I would like to change this so that I can also support landscape for the iPhone 6 Plus, but NOT for other iPhones. I can image one or two solutions, but these are all rather brittle and will probably break when Apple starts to make new devices.

In an ideal world I would like to change the above method to look like the following snippet, where the decision is based on the device's user interface size class instead of the user interface idiom:

- (NSUInteger) supportedInterfaceOrientations
{
  // Note the hypothetical UIDevice method "landscapeSizeClass"
  if ([[UIDevice currentDevice] landscapeSizeClass] == UIUserInterfaceSizeClassCompact)
    return (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown);
  else
    return UIInterfaceOrientationMaskAll;
}

Is there something like this magical landscapeSizeClass method somewhere in UIKit? I have looked around a bit in various class references and guides, but didn't find anything useful. Or can someone suggest a different solution that is similarly generic and future-proof?

Note that my app creates its UI programmatically, so purely storyboard-based solutions are out. Also my app still needs to support iOS 7 so I can't just change everything to use size classes. What I can do, though, is to make runtime checks before I use simple iOS 8 APIs.

herzbube
  • 13,158
  • 9
  • 45
  • 87
  • come up with your own size classes substitute, I did one in my answer here for example http://stackoverflow.com/questions/28423001/how-to-differ-between-ipad-mini-and-ipad-air/28424930#28424930 – Jef Feb 16 '15 at 01:42
  • I've come up with something similar, albeit much simpler than what you did. I will post my solution in a day or two, but let me see first if someone can pull a rabbit from his or her hat :-) – herzbube Feb 16 '15 at 01:53
  • BOOL allowLandscape = ( ( [UIScreen mainScreen].bounds.size.height > 800 ) || ( [UIScreen mainScreen].bounds.size.width > 800 ) ) ; how about that then? – Jef Feb 16 '15 at 07:31

4 Answers4

2

I will rather use macros to discover that (for readability reasons), with something like:

#define IS_IPAD (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
#define IS_IPHONE (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
#define SCREEN_WIDTH ([[UIScreen mainScreen] bounds].size.width)
#define SCREEN_HEIGHT ([[UIScreen mainScreen] bounds].size.height)
#define SCREEN_MAX_LENGTH (MAX(SCREEN_WIDTH, SCREEN_HEIGHT))
#define SCREEN_MIN_LENGTH (MIN(SCREEN_WIDTH, SCREEN_HEIGHT))
#define IS_IPHONE_6P (IS_IPHONE && SCREEN_MAX_LENGTH == 736.0)

And then:

    - (NSUInteger) supportedInterfaceOrientations
    {
      if (IS_IPAD || IS_IPHONE_6P)
      {
        return UIInterfaceOrientationMaskAll;  
      }
      else
      {
        return (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown);
      }
    }

Edited:

It may better to have this one macro instead IS_IPHONE_6P and IS_IPAD:

#define IS_LANDSCAPE_REGULAR_SIZE (SCREEN_MAX_LENGTH >= 736.0)

And then:

if (IS_LANDSCAPE_REGULAR_SIZE) { ... }
fray88
  • 820
  • 1
  • 7
  • 23
  • That's what is called a "brittle" solution. So what if the iPhone 6s+ has a 740 pixel screen? – gnasher729 Apr 02 '15 at 14:23
  • That's why I set "SCREEN_MAX_LENGTH >= 736.0" in the edited solution. But anyway this both should be temporally solutions til apple provides a better way. – fray88 Apr 02 '15 at 15:02
1

Lacking an official Apple API, this is the workaround that I've come up with:

- (NSUInteger) supportedInterfaceOrientations
{
  if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
  {
    // iPhone 5S and below: 320x480
    // iPhone 6: 375x667
    // iPhone 6 Plus: 414x736
    CGSize screenSize = [UIScreen mainScreen].bounds.size;
    // The way how UIScreen reports its bounds has changed in iOS 8.
    // Using MIN() and MAX() makes this code work for all iOS versions.
    CGFloat smallerDimension = MIN(screenSize.width, screenSize.height);
    CGFloat largerDimension = MAX(screenSize.width, screenSize.height);
    if (smallerDimension >= 400 && largerDimension >= 700)
      return UIInterfaceOrientationMaskAll;
    else
      return (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown);
  }
  else
  {
    // Don't need to examine screen dimensions on iPad
    return UIInterfaceOrientationMaskAll;
  }
}

The snippet simply assumes that a screen with dimensions above a semi-arbitrarily chosen size is suitable for rotation. Semi-arbitrarily, because a threshold of 400x700 includes the iPhone 6 Plus, but excludes the iPhone 6.

Although this solution is rather simple, I like it exactly because of its lack of sophistication. I don't really need to distinguish exactly between devices, so any clever solutions such as the one in Jef's answer are overkill for my purposes.

Community
  • 1
  • 1
herzbube
  • 13,158
  • 9
  • 45
  • 87
  • Uh this is pretty much my second comment there exactly mate.. Just remove the userUdiom stuff, it's deprecated and unnecessary, iPad will fall through the correct side of the crack anyway... – Jef Feb 25 '15 at 00:27
  • @Jef I would have asked you to write your comment as an answer except for the userIdiom handling. Where does it say that this is deprecated? And I don't fully agree that it is unnecessary - it may be unnecessary today, but what about tomorrow when Apple brings out a new micro iPad which is slightly smaller than those hardcoded threshold values? – herzbube Feb 25 '15 at 15:13
  • Whoops, my bad. I got it from the WWDC session where they introduced the size classes late last year, perhaps the engineer said it will soon be deprecated and I've confused it. With the 6+ supporting UISplitViewController, UIPopoverController, launching from springboard in landscape etc it's certainly lost any usefulness at any rate.. do you honestly think there might be a future iPad which is smaller than some iPhones? that'd be pretty funny.... – Jef Feb 26 '15 at 07:18
0
- (NSUInteger) supportedInterfaceOrientations {
    if (self.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassRegular ||
        self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassRegular) {
        return UIInterfaceOrientationMaskAll;
    } else {
        return (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown);
    }
}

Update: Previous is not working so here is different solution

- (NSUInteger) supportedInterfaceOrientations {
    if (self.traitCollection.displayScale == 3.0 || (self.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassRegular &&
        self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassRegular)) {
        return UIInterfaceOrientationMaskAll;
    } else {
        return (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown);
    }
}
Silmaril
  • 4,241
  • 20
  • 22
  • That's not working. For instance, your solution returns `UIInterfaceOrientationMaskAll` even for small devices like iPhone 4S, because those have a vertical size class `UIUserInterfaceSizeClassRegular`. And if you're trying to think a way out of this, forget it, it won't work: The size classes for iPhone 6+ in portrait orientation are the same as the size classes for, let's say, iPhone 4S, so the two devices are virtually indistinguishable as long as they're in portrait orientation. – herzbube May 26 '15 at 22:23
  • I'm not convinced. `displayScale` may work for the moment, agreed, but as soon as Apple releases the next generation of iPhones (which probably will all have `displayScale` == 3.0) this solution will probably break again. A reliable solution, IMO, must somehow be able to measure the screen dimensions in landscape, _regardless of the current device orientation_. Whether this measurement is taken in points (my current solution), or in some abstract form (e.g. size class) is secondary. – herzbube May 27 '15 at 20:15
0

Fwiw, the equivalent of herzbube's answer in Swift 1.2:

override func supportedInterfaceOrientations() -> Int {
    if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
        let size = self.view.bounds.size
        let smallerDimension = min(size.width, size.height)
        let largerDimension = max(size.width, size.height)
        if smallerDimension >= 400 && largerDimension >= 700 {
            return Int(UIInterfaceOrientationMask.All.rawValue)
        } else {
            return Int(UIInterfaceOrientationMask.Portrait.rawValue)
        }
    } else {
        return Int(UIInterfaceOrientationMask.All.rawValue)
    }
}

though personally I prefer the following style:

override func supportedInterfaceOrientations() -> Int {

    if UIDevice.currentDevice().userInterfaceIdiom == .Pad {
        return Int(UIInterfaceOrientationMask.All.rawValue)
    }

    let size = self.view.bounds.size
    let smallerDimension = min(size.width, size.height)
    let largerDimension = max(size.width, size.height)

    return smallerDimension >= 400 && largerDimension >= 700 ?
        Int(UIInterfaceOrientationMask.All.rawValue) :
        Int(UIInterfaceOrientationMask.Portrait.rawValue)
}
Community
  • 1
  • 1
mm2001
  • 6,427
  • 5
  • 39
  • 37