objective-cxcodeuiinterfaceorientationios12

Complex Multi-Device Orientation Handling


I have a universal application running both on iPads and iPhones. The application starts with a .xib file, built in interface builder, which acts as the launch image. Once the app launched, it switches to the appropriate view controller based on device size set in the app delegate:

CGSize screenSize = [[UIScreen mainScreen] bounds].size;

if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
    if (screenSize.height <= 568.0f) {
        // iPhone 4, 4S, 5, 5C, 5S, SE
        self.viewController = [[iPhoneSmallViewController alloc] init];
    } else {
        // All other iPhones
        self.viewController = [[iPhoneLargeViewController alloc] init];
    }
} else {
    // All iPad models
    self.viewController = [[iPadViewController alloc] init];
}

The iPad view controller supports all interface orientations (set in app targets/main setup page), but on iPhones I only allow portrait mode restricted in the view controller as such:

- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
    return (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown);
}

I have two problems with this method:

  1. If the iPhone is held horizontally, the app still loads in portrait mode (as per the restrictions, which is all good) but all measurements are in landscape upon initialization. UI elements stick out on the side because they were measured for a landscape view but placed on a portrait.

I use the window's size to set up everything inside the view by initializing the following variable in the ViewDidLoad method:

windowSize = [[UIScreen mainScreen] bounds].size;

Tt gives me landscape dimensions in phone is held horizontally, even though landscape mode is not allowed.

  1. If the app loads with landscape measurements initially, all my sorting of screen sizes in the app delegate are off since I identify iPhone models by measuring screen width that is only good in portrait mode.

Question: does anyone have a way to handle this complex problem in an elegant and simple way?

Some additional info: I use Xcode 10, support all the way back to iOS9 and do everything programmatically in Objective C.

p.s: I think this method worked before but not any more in iOS 12. But I could be wrong...

Edit: I provide an image of what I want to accomplish, and all help would be greatly appreciated. As I said, this has worked before (the app is quite old), but in recent iOS releases got increasingly buggy and desperately needs a cleanup, which is what I need help with.

One thing that might solve my problem, is if I could somehow restrict interface orientations based on device type in the launchScreen.xib, as I believe that is what causes the faulty behavior on iPhones.

enter image description here


Solution

  • I've been experimenting with this for days, and worked out a solution. Although this is probably not the most elegant way to do it, so if anyone has a better solution, please feel free to post it.

    1. It is important to allow all interface orientations in the info.plist because I was unable to restrict them based on device size in the launchScreen.xib.

    2. Create the universal launch screen that supports both iPads and iPhones. Because all interface orientations are allowed in the info.plist, this will have no restrictions.

    3. Below is my current method in the app delegate. This is not the best way to do identifying the smaller iPhones (which I need for reasons... :), but because of the size differences, this works quite well.

    At this point, the phone can be in any of the four interface orientations set in the info.plist, but because only the small handsets have a 320-width it is easy to catch it:

    // Get screen size
    CGSize screenSize = [[UIScreen mainScreen] bounds].size;
    
    // Determine device based on screen dimensions
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
        if ( (screenSize.height == 320.0f) || (screenSize.width == 320.0f) ){
            // iPhone 4, 4S, 5, 5C, 5S, SE
            self.viewController = [[iPhoneSmallViewController alloc] init];
        } else {
            // iPhone 6, 6S, 6P, 6SP, 7, 7P, 8, 8P X, XS, XM, XR
            self.viewController = [[iPhoneLargeViewController alloc] init]; //Same as previous
        }
    } else {
        // All iPad models
        self.viewController = [[iPadViewController alloc] init];
    }
    
    1. Restrict interface orientations in the iPhone view controllers (all other view controllers will inherit the ones we set in the info.plist).

    Do it like so:

    - (UIInterfaceOrientationMask)supportedInterfaceOrientations {
        return (UIInterfaceOrientationMaskPortrait);
    }
    
    1. There is another trick we need to do for the above to work though. When the window loads for the first time, it will not take into consideration the restriction we added to the view controller. That means, if we do our setup in the viewDidLoad method, we will receive landscape screen dimensions if the device is held horizontally (even though this orientation is not allowed). Restriction will be applied once the viewDidLoad method has concluded.

    Therefore, to prevent buggy behavior, you need to create a separate method in which you do your setup (such as postViewDidLoad) and call it once the real viewDidLoad had concluded:

    - (void) viewDidLoad
    {
        [super viewDidLoad];
    
        [self performSelector:@selector(postViewDidLoad) withObject:nil afterDelay:0.0f];
    }
    

    In this method, you will get access the real screen size based on your restrictions you set in the supportedInterfaceOrientations method.

    And that is basically it. If you have multiple views, all with different restrictions, just follow steps 4 and 5 in each of them to properly setup your workflow.