To enable the rotation of a single view controller used to display the preview of Images/Videos. It is intuitive to allow user to rotate there device and screen changes accordingly, so it feels pleasant. But to achieve this, we need to enable the (almost) all Supported Device orientations.
Ex: `Portrait`, `LandscapeLeft`, `LandscapeRight`.
By enabling Supported Device orientations either from Info.plist
or via AppDelegate
.
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return .all
}
By doing this we are allowing other view controllers to also rotate if device orientation changes. But we want only Image/Video Previewing view controller is allowed to Rotate in all orientations.
- Documentation: Handling View Rotation
A view controller can override the `supportedInterfaceOrientations`
method to limit the list of supported orientations.
By doing we can allow our view controllers to limited orientations like below:
class ViewController: UIViewController {
...
override var shouldAutorotate: Bool {
return false
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}
...
}
class PreviewingViewController: UIViewController {
...
override var shouldAutorotate: Bool {
return false
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .allButUpsideDown
}
...
}
The ViewController
is now the rootViewController of current window
and our PreviewingViewController
embedded in UINavigationController
is being presented modally covering the entire screen.
- Result: It worked as desired. 😇
The ViewController
is embedded in UINavigationController
, and PreviewingViewController
is the same as Configuration 1
.
- Result: It's not working now.
ViewController
screen is now rotating again as device orientation changes. 🙁
The ViewController
is embedded in UITabBarController
, and PreviewingViewController
is the same as Configuration 1
.
- Result: Same as result as in
configuration 2
.☹️
Lets put break-point at supportedInterfaceOrientations
on both ViewController
and PreviewingViewController
.
In Configuration 1
: Break-point being hit every time when new orientation is applied on both view-controllers.
In Configuration 2
: Break-point hit for first time, but not when device rotated on both view-controllers.
In Configuration 3
: same as configuration 2
.
A view controller can override the `supportedInterfaceOrientations`
method to limit the list of supported orientations.
Typically, the system calls this method `only` on the
root view controller of the window or
a view controller presented to fill the entire screen;
Yes, this is why Configuration 1
is working properly. In this configuration we have our ViewController
as the only rootViewController of our window.
if let window = (UIApplication.shared.delegate as? AppDelegate)?.window {
print(window.rootViewController is ViewController)
}
// Prints true.
And for the PreviewingViewController
is being presented covering entire screen, so its supportedInterfaceOrientations
property is also called every time device changes to new orientation.
So when the device orientation changes we get called for the appropriate UIInterfaceOrientationMask
####So what's the deal for the Configuration 2
?
Yes, now we have UINavigationController
as our window's rootViewController
.
if let window = (UIApplication.shared.delegate as? AppDelegate)?.window {
print(window.rootViewController is UINavigationController)
}
// Prints true.
We need to provide appropriate supportedInterfaceOrientations
to our UINavigationController controller in order to get notified in ViewController
Lets extend UINavigationController
.
extension UINavigationController {
open override var shouldAutorotate: Bool {
return true
}
open override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return topViewController?.supportedInterfaceOrientations ?? .allButUpsideDown
}
}
Now we have told navigationController
to ask its topViewController
to return appropriate supportedInterfaceOrientations
As we run, we get hit at break point every-time when device is rotated to new orientation.
extension UITabBarController {
open override var shouldAutorotate: Bool {
return true
}
open override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return selectedViewController?.supportedInterfaceOrientations ?? .allButUpsideDown
}
}
As we have told UITabBarController
to ask its selectedViewController
to return its supportedInterfaceOrientations
.
As expected, this is working too, Break-point hit when device is rotated to new orientation. Hurray. 😎
Question: Why we have to extend UINavigationController
or UITabBarController
(ContainerViewController
s) to do rotation according to its children?
Further reading the documentation...
child view controllers use the portion of the window provided
for them by their parent view controller and
no longer participate directly in decisions
about what rotations are supported.
This may be the default implementation for container view controllers.
The sole purpose of this gist to understand rotation behaviour of child viewControllers in ContainerViewController like UINavigationController, UITabBarControllers, UISplitViewControllers etc.
To implement proper rotation behaviour in our viewControllers, we should subclass these ContainerViewController
and then override these properties, because extending these UIKit
Classes globally will cause unexpected behaviour as mensioned in Customizing Existing Classes.
If the name of a method declared in a category is the same as a method in the original class, or a method in another category on the same class (or even a superclass), the behavior is undefined as to which method implementation is used at runtime. This is less likely to be an issue if you’re using categories with your own classes, but can cause problems when using categories to add methods to standard Cocoa or Cocoa Touch classes.
Thanks.
Nice Solution without SubClass
UINavigationController
,UINavigationControllerDeleagte
.Thanks. 😀