/ScreenRotator

A utility class that allows for rotating/locking screen orientation anytime, anywhere. 屏幕旋转工具类,能通过代码随时随地旋转/锁定屏幕方向。

Primary LanguageSwiftMIT LicenseMIT

ScreenRotator

A utility class that allows for rotating/locking screen orientation anytime, anywhere.

Features:
    ✅ Supports rotation control in four directions:
        - Portrait: Top of the phone is up
        - Portrait: Top of the phone is down
        - Landscape: Top of the phone is to the left
        - Landscape: Top of the phone is to the right
    ✅ Allows control over whether the screen orientation changes automatically with device movement;
    ✅ Compatible with iOS 16;
    ✅ Supports Objective-C, Swift, and SwiftUI;
    ✅ Simple and easy-to-use API.

Usage Examples:

  • Rotating/Locking Screen Orientation Anytime, Anywhere

ScreenRotator_1.gif

  • push or present a new page with a different orientation than the current one

ScreenRotator_2.gif

  • Switching between portrait and landscape modes in videos

ScreenRotator_3.gif

Prerequisites:

  1. To globally control screen orientation with the singleton ScreenRotator.shared, override the following method in AppDelegate:
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
    return ScreenRotator.shared.orientationMask
}
  1. No need to override supportedInterfaceOrientations and shouldAutorotate in UIViewController anymore.

  2. If you need to obtain real-time screen dimensions, override the following method in the respective ViewController:

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    // Example: Portrait --> Landscape
    
    // When the screen rotates, this function is automatically triggered by the system, and `size` represents the screen size after the rotation.
    print("size \(size)") // --- (926.0, 428.0)
    // Alternatively, you can also obtain the screen size after rotation through `UIScreen`.
    print("mainScreen \(UIScreen.main.bounds.size)") // --- (926.0, 428.0)

    // 📢 Note: If you attempt to retrieve screen-related information using `self.xxx` (such as `self.view.frame`), the size obtained at this point is still the size before the rotation.
    print("----------- Screen will rotate -----------")
    print("view.size \(view.frame.size)") // - (428.0, 926.0)
    print("window.size \(view.window?.size ?? .zero)") // - (428.0, 926.0)
    print("window.safeAreaInsets \(view.window?.safeAreaInsets ?? .zero)") // - UIEdgeInsets(top: 47.0, left: 0.0, bottom: 34.0, right: 0.0)
    
    // 📢 To obtain screen information after rotation, you need to wait until the next iteration of the `Runloop`.
    DispatchQueue.main.async {
        print("----------- Screen has rotated -----------")
        print("view.size \(self.view.frame.size)") // - (926.0, 428.0)
        print("window.size \(self.view.window?.size ?? .zero)") // - (926.0, 428.0)
        print("window.safeAreaInsets \(self.view.window?.safeAreaInsets ?? .zero)") // - UIEdgeInsets(top: 0.0, left: 47.0, bottom: 21.0, right: 47.0)
        print("==================================")
    }
}
  1. If you need to listen for screen rotation, use ScreenRotator.orientationDidChangeNotification notification provided by this utility class or implement using closure:
ScreenRotator.shared.orientationMaskDidChange = { orientationMask in 
    // Update the orientation of the FunnyButton belonging to the window.
    FunnyButton.orientationMask = orientationMask
}
  1. For iPad App, you need to go to TARGETS -> General -> Deployment Info -> enable Requires full screen to use code to rotate the screen:

ScreenRotator_4.jpg

API:

Methods available through the singleton ScreenRotator.shared:

  1. Rotate to target orientation
func rotation(to orientation: Orientation)
  1. Rotate to Portrait (Top of the phone is up)
func rotationToPortrait()
  1. Rotate to Portrait (Top of the phone is down)
func rotationToPortraitUpsideDown()
  1. Rotate to Landscape (If screen is locked, rotates to left side of the phone)
func rotationToLandscape()
  1. Rotate to Landscape (Top of the phone is to the left)
func rotationToLandscapeLeft()
  1. Rotate to Landscape (Top of the phone is to the right)
func rotationToLandscapeRight()
  1. Toggle between Portrait and Landscape
func toggleOrientation()
  1. Is in Portrait mode (Top of the phone is up)
var isPortrait: Bool
  1. Current screen orientation (ScreenRotator.Orientation)
var orientation: Orientation
  1. Allow rotation to Portrait (Top of the phone is down) (Default is false)
var isAllowPortraitUpsideDown: Bool = false
  1. Lock screen orientation (If Portrait Orientation Lock is disabled in Control Center, setting this to true will prevent automatic screen rotation based on device movement)
var isLockOrientationWhenDeviceOrientationDidChange = true 
// Note: Even if locked (`true`), you can still change screen orientation using this tool
  1. Lock Landscape orientation (If Portrait Orientation Lock is disabled in Control Center, setting this to true will allow screen rotation based on device movement, but only for landscape orientations)
var isLockLandscapeWhenDeviceOrientationDidChange = false 
// Note: Even if locked (`true`), you can still change screen orientation using this tool
  1. Closure to handle changes in screen orientation:
var orientationMaskDidChange: ((_ orientationMask: UIInterfaceOrientationMask) -> ())?
  1. Closure to handle changes in lock status for screen orientation:
var lockOrientationWhenDeviceOrientationDidChange: ((_ isLock: Bool) -> ())?
  1. Closure to handle changes in lock status for landscape orientation:
var lockLandscapeWhenDeviceOrientationDidChange: ((_ isLock: Bool) -> ())?

Observable Notifications:

  1. Notification for changes in screen orientation:
  • ScreenRotator.orientationDidChangeNotification
    • object: orientationMask (UIInterfaceOrientationMask)
  1. Notification for changes in lock status for screen orientation:
  • ScreenRotator.lockOrientationWhenDeviceOrientationDidChangeNotification
    • object: isLockOrientationWhenDeviceOrientationDidChange (Bool)
  1. Notification for changes in lock status for landscape orientation:
  • ScreenRotator.lockLandscapeWhenDeviceOrientationDidChangeNotification
    • object: isLockLandscapeWhenDeviceOrientationDidChange (Bool)

Compatibility with OC & SwiftUI:

  • Objective-C: Use JPScreenRotator, which is specifically written in OC, with the same usage as ScreenRotator.

  • SwiftUI: Use ScreenRotatorState to update state.

    • Refer to the RotatorView in the Demo for usage details.

Tips:

When push or present a new page with a different orientation than the current one, it is recommended to rotate first and then open after a delay of at least 0.1s to avoid screen orientation confusion. Example:

let testVC = UIViewController()

// 1. Rotate first
ScreenRotator.shared.rotation(to: .landscapeRight)

// 2. Open after a delay of at least 0.1s
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) {
    if let navCtr = self.navigationController {
        navCtr.pushViewController(testVC, animated: true)
    } else {
        self.present(testVC, animated: true)
    }  
}

Installation

ScreenRotator is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'ScreenRotator'

中文介绍

屏幕旋转工具类,能通过代码随时随地旋转/锁定屏幕方向。

Feature:
    ✅ 可控制旋转四个方向:
        - 竖屏:手机头在上边
        - 竖屏:手机头在下边
        - 横屏:手机头在左边
        - 横屏:手机头在右边
    ✅ 可控制是否随手机摆动自动改变屏幕方向;
    ✅ 适配iOS16;
    ✅ 兼容 OC & Swift & SwiftUI;
    ✅ API简单易用。

使用效果

  • 随时随地旋转/锁定屏幕方向

ScreenRotator_1.gif

  • pushpresent一个跟当前方向不一样的新页面

ScreenRotator_2.gif

  • 视频的横竖屏切换

ScreenRotator_3.gif

使用前提

  1. 让单例ScreenRotator.shared全局控制屏幕方向,首先得在AppDelegate中重写:
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
    return ScreenRotator.shared.orientationMask
}
  1. 不需要再重写UIViewControllersupportedInterfaceOrientationsshouldAutorotate

  2. 如需获取屏幕实时尺寸,在对应ViewController中重写:

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    // 🌰🌰🌰:竖屏 --> 横屏
    
    // 当屏幕发生旋转时,系统会自动触发该函数,`size`为【旋转之后】的屏幕尺寸
    print("size \(size)") // --- (926.0, 428.0)
    // 或者通过`UIScreen`也能获取【旋转之后】的屏幕尺寸
    print("mainScreen \(UIScreen.main.bounds.size)") // --- (926.0, 428.0)

    // 📢 注意:如果想通过`self.xxx`去获取屏幕相关的信息(如`self.view.frame`),【此时】获取的尺寸还是【旋转之前】的尺寸
    print("----------- 屏幕即将旋转 -----------")
    print("view.size \(view.frame.size)") // - (428.0, 926.0)
    print("window.size \(view.window?.size ?? .zero)") // - (428.0, 926.0)
    print("window.safeAreaInsets \(view.window?.safeAreaInsets ?? .zero)") // - UIEdgeInsets(top: 47.0, left: 0.0, bottom: 34.0, right: 0.0)
    
    // 📢 想要获取【旋转之后】的屏幕信息,需要到`Runloop`的下一个循环才能获取
    DispatchQueue.main.async {
        print("----------- 屏幕已经旋转 -----------")
        print("view.size \(self.view.frame.size)") // - (926.0, 428.0)
        print("window.size \(self.view.window?.size ?? .zero)") // - (926.0, 428.0)
        print("window.safeAreaInsets \(self.view.window?.safeAreaInsets ?? .zero)") // - UIEdgeInsets(top: 0.0, left: 47.0, bottom: 21.0, right: 47.0)
        print("==================================")
    }
}
  1. 如需监听屏幕的旋转,不用再监听UIDevice.orientationDidChangeNotification通知,而是监听该工具类提供的ScreenRotator.orientationDidChangeNotification通知。或者通过闭包的形式实现监听:
ScreenRotator.shard.orientationMaskDidChange = { orientationMask in 
    // 更新`FunnyButton`所属`window`的方向
    FunnyButton.orientationMask = orientationMask
}
  1. 如果是iPad App,需要去TARGETS -> General -> Deployment Info -> 打开Requires full screen,方可使用代码旋转屏幕:

ScreenRotator_4.jpg

API

全局使用单例ScreenRotator.shared调用:

  1. 旋转至目标方向
func rotation(to orientation: Orientation)
  1. 旋转至竖屏(手机头在上边)
func rotationToPortrait()
  1. 旋转至竖屏(手机头在下边)
func rotationToPortraitUpsideDown()
  1. 旋转至横屏(如果锁定了屏幕,则转向手机头在左边)
func rotationToLandscape()
  1. 旋转至横屏(手机头在左边)
func rotationToLandscapeLeft()
  1. 旋转至横屏(手机头在右边)
func rotationToLandscapeRight()
  1. 横竖屏切换
func toggleOrientation()
  1. 是否正在竖屏(手机头在上边)
var isPortrait: Bool
  1. 当前屏幕方向(ScreenRotator.Orientation)
var orientation: Orientation
  1. 是否允许转向竖屏-手机头在下边的方向(默认不允许)
var isAllowPortraitUpsideDown: Bool = false
  1. 是否锁定屏幕方向(当控制中心禁止了竖屏锁定,为true则不会【随手机摆动自动改变】屏幕方向)
var isLockOrientationWhenDeviceOrientationDidChange = true 
// PS:即便锁定了(`true`)也能通过该工具类去旋转屏幕方向
  1. 是否锁定横屏方向(当控制中心禁止了竖屏锁定,为true则【仅限横屏的两个方向会随手机摆动自动改变】屏幕方向)
var isLockLandscapeWhenDeviceOrientationDidChange = false 
// PS:即便锁定了(`true`)也能通过该工具类去旋转屏幕方向
  1. <屏幕方向>发生改变的回调闭包
var orientationMaskDidChange: ((_ orientationMask: UIInterfaceOrientationMask) -> ())?
  1. <是否锁定屏幕方向>发生改变的回调闭包
var lockOrientationWhenDeviceOrientationDidChange: ((_ isLock: Bool) -> ())?
  1. <是否锁定横屏方向>发生改变的回调闭包
var lockLandscapeWhenDeviceOrientationDidChange: ((_ isLock: Bool) -> ())?

可监听的通知

  1. <屏幕方向>发生改变的通知:
  • ScreenRotator.orientationDidChangeNotification
    • object: orientationMask(UIInterfaceOrientationMask)
  1. <是否锁定屏幕方向>发生改变的通知:
  • ScreenRotator.lockOrientationWhenDeviceOrientationDidChangeNotification
    • object: isLockOrientationWhenDeviceOrientationDidChange(Bool)
  1. <是否锁定横屏方向>发生改变的通知:
  • ScreenRotator.lockLandscapeWhenDeviceOrientationDidChangeNotification
    • object: isLockLandscapeWhenDeviceOrientationDidChange(Bool)

兼容 OC & SwiftUI

  • OC:使用专门用OC写的JPScreenRotator,用法和ScreenRotator完全一致。

  • SwiftUI:可以通过ScreenRotatorState来更新状态。

    • 具体使用可以参考Demo中的RotatorView

Tips

pushpresent一个跟当前方向不一样的新页面时,建议先旋转,再延时至少0.1s才打开,否则新页面的屏幕方向会错乱。例如:

let testVC = UIViewController()

// 1.先旋转
ScreenRotator.shared.rotation(to: .landscapeRight)

// 2.延时至少0.1s再打开
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) {
    if let navCtr = self.navigationController {
        navCtr.pushViewController(testVC, animated: true)
    } else {
        self.present(testVC, animated: true)
    }  
}

安装

ScreenRotator 可通过CocoaPods安装,只需添加下面一行到你的podfile:

pod 'ScreenRotator'