Last active
May 27, 2024 12:11
-
-
Save ajself/173778ad6104870b541c7549805801e0 to your computer and use it in GitHub Desktop.
Rotate UIViewControllers that conform to a protocol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
This is an update to an example found on http://www.jairobjunior.com/blog/2016/03/05/how-to-rotate-only-one-view-controller-to-landscape-in-ios-slash-swift/ | |
The code there works, with some updating to the latest Swift, but the pattern isn't very Swifty. The following is what I found to be more helpful. | |
*/ | |
/* | |
First, create a protocol that UIViewController's can conform to. | |
This is in opposition to using `Selector()` and checking for the presence of an empty function. | |
*/ | |
/// UIViewControllers adopting this protocol will automatically be opted into rotating to all but bottom rotation. | |
/// | |
/// - Important: | |
/// You must call resetToPortrait as the view controller is removed from view. Example: | |
/// | |
/// ``` | |
/// override func viewWillDisappear(_ animated: Bool) { | |
/// super.viewWillDisappear(animated) | |
/// | |
/// if isMovingFromParentViewController { | |
/// resetToPortrait() | |
/// } | |
/// } | |
/// ``` | |
protocol Rotatable: AnyObject { | |
func resetToPortrait() | |
} | |
extension Rotatable where Self: UIViewController { | |
func resetToPortrait() { | |
UIDevice.current.setValue(Int(UIInterfaceOrientation.portrait.rawValue), forKey: "orientation") | |
} | |
} | |
/* | |
Next, extend AppDelegate to check for VCs that conform to Rotatable. If they do allow device rotation. | |
Remember, it's up to the conforming VC to reset the device rotation back to portrait. | |
*/ | |
// MARK: - Device rotation support | |
extension AppDelegate { | |
// The app disables rotation for all view controllers except for a few that opt-in by conforming to the Rotatable protocol | |
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask { | |
guard | |
let _ = topViewController(for: window?.rootViewController) as? Rotatable | |
else { return .portrait } | |
return .allButUpsideDown | |
} | |
private func topViewController(for rootViewController: UIViewController!) -> UIViewController? { | |
guard let rootVC = rootViewController else { return nil } | |
if rootVC is UITabBarController { | |
let rootTabBarVC = rootVC as! UITabBarController | |
return topViewController(for: rootTabBarVC.selectedViewController) | |
} else if rootVC is UINavigationController { | |
let rootNavVC = rootVC as! UINavigationController | |
return topViewController(for: rootNavVC.visibleViewController) | |
} else if let rootPresentedVC = rootVC.presentedViewController { | |
return topViewController(for: rootPresentedVC) | |
} | |
return rootViewController | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thank you so much for this gist, it's been really useful. I've been running into a small problem though.
I need a view in my hierarchy to be rotatable to present a video. However, the view before, which it will return to, can absolutely not be rotated (it's a complicated view that breaks if it is rotated). Since I'm using
AVPlayerViewController
, I can't actually catch the "done" button action from the user to reset the rotation before asking to dismiss the controller.Calling
resetToPortrait
inviewWillDisappear
of theAVPlayerViewController
sadly doesn't help, because the rotation will somehow only follow after the view has already disappeared. Even when presenting theAVPlayerViewController
from anotherViewController
that is dismissed whenAVPlayerViewController
is dismissed, the rotation occurs too late.Do you have a suggestion for making a controller wait for the rotation to actually occur before it dismisses itself?