import CoreMotion import UIKit public class OrientationTracker { public static let shared = OrientationTracker() public static let deviceOrientationChangedNotification = NSNotification.Name("DeviceOrientationChangedNotification") public var currentDeviceOrientation: UIDeviceOrientation = .portrait public var currentInterfaceOrientation: UIInterfaceOrientation { switch currentDeviceOrientation { case .landscapeLeft: return .landscapeLeft case .landscapeRight: return .landscapeRight default: return .portrait } } public var currentInterfaceOrientationMask: UIInterfaceOrientationMask { switch currentInterfaceOrientation { case .landscapeLeft: return .landscapeLeft case .landscapeRight: return .landscapeRight default: return .portrait } } public var affineTransform: CGAffineTransform { var angleRadians: Double switch currentDeviceOrientation { case .portrait: angleRadians = 0 case .landscapeLeft: angleRadians = -0.5 * .pi case .landscapeRight: angleRadians = 0.5 * .pi case .portraitUpsideDown: angleRadians = .pi default: return .identity } return CGAffineTransform(rotationAngle: angleRadians) } private let motionManager: CMMotionManager private let queue: OperationQueue private init() { motionManager = CMMotionManager() motionManager.accelerometerUpdateInterval = 0.1 queue = OperationQueue() } public func startDeviceOrientationTracking() { motionManager.startAccelerometerUpdates(to: queue) { accelerometerData, error in guard error == nil else { return } guard let accelerometerData else { return } let newDeviceOrientation = self.deviceOrientation(forAccelerometerData: accelerometerData) guard newDeviceOrientation != self.currentDeviceOrientation else { return } self.currentDeviceOrientation = newDeviceOrientation NotificationCenter.default.post(name: Self.deviceOrientationChangedNotification, object: nil, userInfo: nil) } } public func stopDeviceOrientationTracking() { motionManager.stopAccelerometerUpdates() } private func deviceOrientation(forAccelerometerData accelerometerData: CMAccelerometerData) -> UIDeviceOrientation { let threshold = 0.55 if accelerometerData.acceleration.x >= threshold { return .landscapeLeft } else if accelerometerData.acceleration.x <= -threshold { return .landscapeRight } else if accelerometerData.acceleration.y <= -threshold { return .portrait } else if accelerometerData.acceleration.y >= threshold { return .portraitUpsideDown } else { return currentDeviceOrientation } } }