Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to prevent ARSCNView from rotating during orientation change

I am using ARKit and I want to allow the users to use the app in both portrait and landscape mode. I would like all UI controls to rotate on orientation change except for the ARSCNView. I tried to transform the sceneView in the opposite direction but that didn't work.

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)

        let targetRotation = coordinator.targetTransform
        let inverseRotation = targetRotation.inverted()

        coordinator.animate(alongsideTransition: { (context) in
            self.sceneView.transform = self.sceneView.transform.concatenating(inverseRotation)
            context.viewController(forKey: UITransitionContextViewControllerKey.from)
        }, completion: nil)

    }

How can I prevent the scene view of the ARKit session from rotating while allowing all other UI controls to rotate on orientation change?

like image 534
Praveen Gowda I V Avatar asked Oct 03 '17 23:10

Praveen Gowda I V


1 Answers

You cannot specify device rotation rules on view basis. It has to be set on view controller basis. This is how iOS works. Thus, to achieve what you need you have to handle this by yourself. For example, if you're showing your ARSCNView as a full screen view, then you can present it inside a custom UIViewController sub-class, and set the rotation configuration for that controller.

Setting the supported view rotations for a specific view controller can be implemented in many ways, below are some of them.


Approach #1:

You can set the supported view orientations for any UIViewController by overriding your app delegate's method application:supportedInterfaceOrientationsForWindow:.

Sample code:

// this goes into your AppDelegate...
// Basically, this checks the current visible view controller  type  and  decide 
// what orientations your app supports based on that view controller type (class)
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
    let visibleViewController = self.topViewController(withRootViewController: window?.rootViewController)

    // Support only portrait orientation for a specific view controller
    if visibleViewController is SomeViewController {
        return .portrait
    }

    // Otherwise, support all orientations (standard behaviour)
    return .allButUpsideDown
}

// This simple helper method is to extract the exact visible  view  controller  at
// the moment, as `window.rootViewController` could have some container controller 
// like `UINavigationController` or so that holds more controllers into it
private func topViewController(withRootViewController rootViewController: UIViewController?) -> UIViewController? {
    if let rootViewController = rootViewController as? UITabBarController {
        return rootViewController.selectedViewController
    } else if let rootViewController = rootViewController as? UINavigationController {
        return rootViewController.visibleViewController
    } else if let presentedViewController = rootViewController?.presentedViewController {
        return topViewController(withRootViewController: presentedViewController)
    }
    return rootViewController
}

Reference:

application:supportedInterfaceOrientationsForWindow: method documentation


Approach #2:

Sub-class your UINavigationController and override the following property:

override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    return self.topViewController?.supportedInterfaceOrientations ?? .all
}

Now this always looks into your view controller's supportedInterfaceOrientations and set the supported orientations based on the return value. Doing this enables you to simply override this in any view controller you want, setting some custom value.

For example, in SomeViewController you could simply add:

override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    return .portrait
}

Reference:

supportedInterfaceOrientations property documentation


Approach #3:

If you don't want to sub-class your UINavigationController like in approach #2 above, you can set your SomeViewController as the navigation controller delegate implementing navigationControllerSupportedInterfaceOrientations(:)

Sample code:

override func viewDidLoad() {
    super.viewDidLoad()

    // ...

    self.navigationController?.delegate = self
}

// MARK: - UINavigationControllerDelegate

func navigationControllerSupportedInterfaceOrientations(_ navigationController: UINavigationController) -> UIInterfaceOrientationMask {
    return navigationController.topViewController?.supportedInterfaceOrientations ?? .all
}

Reference:

Matt's answer here

like image 194
Mamouneyya Avatar answered Oct 20 '22 13:10

Mamouneyya