iosuiviewcontrolleruiinterfaceorientationinfo.plistshouldautorotate

Supported Interface Orientations in info.plist seem to block calls to shouldAutorotate


I’m designing an iPad/iPhone application in which the interface is designed to look as good in landscape as in portrait, and I’d like the interface to appear at launch as gracefully in one orientation as the other. At the same time, I need the ability to enable/disable autorotations at various times. I think I know how to do both these things, but they don’t seem to play nicely together.

I can set permitted device orientations in the Xcode project, and this seems to create a couple of array items in the project info.plist: "Supported interface orientations (iPad)" and "Supported interface orientations (iPhone)" which list the allowed orientations for each type of device. The presence of these items makes transitions from the launch screen as smooth as silk in all orientations.

I can enable/disable autorotations by overriding shouldAutorotate in my root view controller (there is only one controller at the moment). The problem is that the presence of Supported Interface items in info.plist seem to suppress all calls to shouldAutorotate, thus rendering control of autorotation inoperative.

class   RootController: UIViewController
  {
    var allowAutorotation = true    //  My autorotation switch.
      { didSet    { guard   allowAutorotation   else  { return }
                    UIViewController.attemptRotationToDeviceOrientation()
                    /*  Do other stuff.   */ }   }

    override var supportedInterfaceOrientations: UIInterfaceOrientationMask
      { NSLog ("RootController: supportedInterfaceOrientations")
        return .allButUpsideDown  }

    override var shouldAutorotate: Bool
      { NSLog ("RootController: shouldAutorotate")
        return allowAutorotation  }

    override func viewDidLoad()
      { allowAutorotation = true
        super.viewDidLoad()  }

         }

I’m using viewDidLoad to call attemptRotationToDeviceOrientation() at the earliest possible moment. If I don’t do this, the app screen appears in Portrait even when the device is in Landscape. (It appears correctly for Portrait, but Portrait isn’t correct.) I’ve tried making a similar call from other places, including didFinishLaunchingWithOptions in the app delegate, which I think may be the most logical place for it, but it doesn’t seem to make a difference.

If I remove the Supported Orientation items from info.plist, and define allowed orientations in code as shown, I can see from my NSLog telltales that calls to shouldAutorotate do occur at appropriate moments, but the launch then looks a trifle awkward in landscape. In the launch screen, the status bar is oriented wrong: along one of the vertical edges rather than at top, and when the transition to the app screen comes, it typically fades in canted about 10-20° off of horizontal. It instantly snaps to horizontal (it’s so quick, in fact, that it’s sometimes difficult to see), and the status bar is then correctly at the top, and from that moment on everything looks good, but the effect still seems a little unprofessional.

Since it only occurs momentarily, and only once at launch, I suppose I could live with it (I can’t live without an autorotation control) but I was hoping someone could suggest a way to get shouldAutorotate calls to work even with orientations defined in info.plist. Or perhaps some other strategy?


Solution

  • I think I have workarounds for these problems. To eliminate the anomalous rotation animation as the Launch Screen fades out, I use CATransactions to disable implicit animations until the app becomes active for the first time:

    class RootController: UIViewController  {
    
    private var appIsLaunching = true  //  Set false when app first becomes active. Never set true again.
    
    func appDidBecomeActive()
      { if appIsLaunching  { appIsLaunching = false; CATransaction.setDisableActions (false) } } 
    
    override func viewWillTransition (to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)
      { if appIsLaunching  { CATransaction.setDisableActions (true) } 
        super.viewWillTransition (to: size, with: coordinator) }
    
            }
    

    And in the appDelegate:

    func applicationDidBecomeActive ( _ application: UIApplication )
      { let root = window!.rootViewController as! RootController; root.appDidBecomeActive() }
    

    I believe this works because a default CATransaction is always in effect, even without an explicit transaction. The cross-dissolve from Launch Screen to first view seems perfect.

    There remains the status bar appearing in the wrong orientation in the Launch Screen. I’ve concluded it’s best to just turn it off, either in the project settings or by setting Status bar is initially hidden in info.plist. Not ideal, but acceptable.