109 lines
5.5 KiB
Swift
109 lines
5.5 KiB
Swift
//
|
||
// CameraManager+MotionManager.swift of MijickCamera
|
||
//
|
||
// Created by Tomasz Kurylik. Sending ❤️ from Kraków!
|
||
// - Mail: tomasz.kurylik@mijick.com
|
||
// - GitHub: https://github.com/FulcrumOne
|
||
// - Medium: https://medium.com/@mijick
|
||
//
|
||
// Copyright ©2024 Mijick. All rights reserved.
|
||
|
||
|
||
import CoreMotion
|
||
import AVKit
|
||
|
||
@MainActor class CameraManagerMotionManager {
|
||
private(set) var parent: CameraManager!
|
||
private(set) var manager: CMMotionManager = .init()
|
||
}
|
||
|
||
// MARK: Setup
|
||
extension CameraManagerMotionManager {
|
||
func setup(parent: CameraManager) {
|
||
self.parent = parent
|
||
manager.accelerometerUpdateInterval = 0.05
|
||
manager.startAccelerometerUpdates(to: .current ?? .init(), withHandler: handleAccelerometerUpdates)
|
||
}
|
||
}
|
||
private extension CameraManagerMotionManager {
|
||
func handleAccelerometerUpdates(_ data: CMAccelerometerData?, _ error: Error?) {
|
||
guard let data, error == nil else { return }
|
||
|
||
let newDeviceOrientation = getDeviceOrientation(data.acceleration)
|
||
updateDeviceOrientation(newDeviceOrientation)
|
||
updateUserBlockedScreenRotation()
|
||
updateFrameOrientation()
|
||
redrawGrid()
|
||
}
|
||
}
|
||
private extension CameraManagerMotionManager {
|
||
func getDeviceOrientation(_ acceleration: CMAcceleration) -> AVCaptureVideoOrientation { switch acceleration {
|
||
case let acceleration where acceleration.x >= 0.75: .landscapeLeft
|
||
case let acceleration where acceleration.x <= -0.75: .landscapeRight
|
||
case let acceleration where acceleration.y <= -0.75: .portrait
|
||
case let acceleration where acceleration.y >= 0.75: .portraitUpsideDown
|
||
default: parent.attributes.deviceOrientation
|
||
}}
|
||
func updateDeviceOrientation(_ newDeviceOrientation: AVCaptureVideoOrientation) { if newDeviceOrientation != parent.attributes.deviceOrientation {
|
||
parent.attributes.deviceOrientation = newDeviceOrientation
|
||
}}
|
||
func updateUserBlockedScreenRotation() {
|
||
let newUserBlockedScreenRotation = getNewUserBlockedScreenRotation()
|
||
if newUserBlockedScreenRotation != parent.attributes.userBlockedScreenRotation { parent.attributes.userBlockedScreenRotation = newUserBlockedScreenRotation }
|
||
}
|
||
func updateFrameOrientation() { if UIDevice.current.orientation != .portraitUpsideDown {
|
||
let newFrameOrientation = getNewFrameOrientation(parent.attributes.orientationLocked ? .portrait : UIDevice.current.orientation)
|
||
updateFrameOrientation(newFrameOrientation)
|
||
}}
|
||
func redrawGrid() { if !parent.attributes.orientationLocked {
|
||
parent.cameraGridView.draw(.zero)
|
||
}}
|
||
}
|
||
private extension CameraManagerMotionManager {
|
||
func getNewUserBlockedScreenRotation() -> Bool { switch parent.attributes.deviceOrientation.rawValue == UIDevice.current.orientation.rawValue {
|
||
case true: false
|
||
case false: !parent.attributes.orientationLocked
|
||
}}
|
||
func getNewFrameOrientation(_ orientation: UIDeviceOrientation) -> CGImagePropertyOrientation { switch parent.attributes.cameraPosition {
|
||
case .back: getNewFrameOrientationForBackCamera(orientation)
|
||
case .front: getNewFrameOrientationForFrontCamera(orientation)
|
||
}}
|
||
func updateFrameOrientation(_ newFrameOrientation: CGImagePropertyOrientation) { if newFrameOrientation != parent.attributes.frameOrientation {
|
||
let shouldAnimate = shouldAnimateFrameOrientationChange(newFrameOrientation)
|
||
updateFrameOrientation(withAnimation: shouldAnimate, newFrameOrientation: newFrameOrientation)
|
||
}}
|
||
}
|
||
private extension CameraManagerMotionManager {
|
||
func getNewFrameOrientationForBackCamera(_ orientation: UIDeviceOrientation) -> CGImagePropertyOrientation { switch orientation {
|
||
case .portrait: parent.attributes.mirrorOutput ? .leftMirrored : .right
|
||
case .landscapeLeft: parent.attributes.mirrorOutput ? .upMirrored : .up
|
||
case .landscapeRight: parent.attributes.mirrorOutput ? .downMirrored : .down
|
||
default: parent.attributes.mirrorOutput ? .leftMirrored : .right
|
||
}}
|
||
func getNewFrameOrientationForFrontCamera(_ orientation: UIDeviceOrientation) -> CGImagePropertyOrientation { switch orientation {
|
||
case .portrait: parent.attributes.mirrorOutput ? .right : .leftMirrored
|
||
case .landscapeLeft: parent.attributes.mirrorOutput ? .down : .downMirrored
|
||
case .landscapeRight: parent.attributes.mirrorOutput ? .up : .upMirrored
|
||
default: parent.attributes.mirrorOutput ? .right : .leftMirrored
|
||
}}
|
||
func shouldAnimateFrameOrientationChange(_ newFrameOrientation: CGImagePropertyOrientation) -> Bool {
|
||
let backCameraOrientations: [CGImagePropertyOrientation] = [.left, .right, .up, .down],
|
||
frontCameraOrientations: [CGImagePropertyOrientation] = [.leftMirrored, .rightMirrored, .upMirrored, .downMirrored]
|
||
|
||
return (backCameraOrientations.contains(newFrameOrientation) && backCameraOrientations.contains(parent.attributes.frameOrientation)) ||
|
||
(frontCameraOrientations.contains(parent.attributes.frameOrientation) && frontCameraOrientations.contains(newFrameOrientation))
|
||
}
|
||
func updateFrameOrientation(withAnimation shouldAnimate: Bool, newFrameOrientation: CGImagePropertyOrientation) { Task {
|
||
await parent.cameraMetalView.beginCameraOrientationAnimation(if: shouldAnimate)
|
||
parent.attributes.frameOrientation = newFrameOrientation
|
||
parent.cameraMetalView.finishCameraOrientationAnimation(if: shouldAnimate)
|
||
}}
|
||
}
|
||
|
||
// MARK: Reset
|
||
extension CameraManagerMotionManager {
|
||
func reset() {
|
||
manager.stopAccelerometerUpdates()
|
||
}
|
||
}
|