【Swift5】デバイスを回転させるとカメラ(AVCaptureVideoPreviewLayer)の向きがおかしくなる現象の解決方法

4 min read読了の目安(約4300字

何が発生しているのか

AVCaptureVideoPreviewLayerでカメラを使うと、デバイスが回転した際、カメラが表示しているのがデバイスの向きと一致しない。

以下のコードはSwiftでカメラアプリを作成する(1)に追記する形になります。

対策

レイヤーの向き

1.画面の向きを簡単に表すenumを作成

ViewController.swift
    /// 画面の向き
    enum Orientaion: Int {
        /// ホームボタンの位置が
        case right = -1,
             down = 0,
             left = 1,
             up = 2
    }

2.画面の向きを取得する関数

ViewController.swift
/// 画面の向きからオリエンテーションを取得する
private func getDeviceOrientation() -> Orientaion {
    
    guard let interfaceOrientation = UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.windowScene?.interfaceOrientation else {
        return .right
    }
    
    switch interfaceOrientation {
    case .landscapeLeft:
        return .left
    case .landscapeRight:
        return .right
    case .portrait:
        return .down
    case .portraitUpsideDown:
        return .up
        
    default:
        return .right
    }
    
}

3.カメラの向きを修正する関数

ViewController.swift
/// カメラの向きを修正する
private func fixCameraOrientation() {
    
    // ロード時からいくら回転したかを取得
    let rotation: CGFloat = CGFloat(getDeviceOrientation().rawValue) * 90
    
    // レイヤーなのでCATransform3DMakeRotationを使って回転させます
    self.cameraPreviewLayer?.transform = CATransform3DMakeRotation(
        rotation / 180 * CGFloat.pi,
        0,
        0,
        1
    )
    
    // 回転してもフレームは前のままなので、親ビューと合わせてあげます
    cameraPreviewLayer?.frame = view.frame
}

/// 画面の向きについてセットアップする
private func setUpOrientaion() {
    
    
    // 通常の状態との違いの角度を取得
    let rotation: CGFloat = {
        switch initialOrientation {
        case .down:
            return 0
            
        case .right:
            return -90
            
        case .up:
            return 180
            
        case .left:
            return 90
            
        }
        
    }()
    
    self.cameraPreviewLayer?.transform = CATransform3DMakeRotation(
        rotation / 180 * CGFloat.pi,
        0,
        0,
        1
    )
    
    // フレームを調整
    cameraPreviewLayer?.frame = view.frame

}

4.画面ロード時と画面回転時に修正をする

ViewController.swift
override func viewDidLoad() {
    super.viewDidLoad()
    
    //カメラの向きを設定
    setUpOrientaion()
    
}

//画面回転時の動作
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)
    coordinator.animate(
        alongsideTransition: { _ in
            
            //カメラの向きを修正
            self.fixCameraOrientation()
            
        }
    )
}

写真の向き

レイヤーの向きと一緒に、写真の向きもおかしくなるので修正します

ViewController.swift
//MARK: AVCapturePhotoCaptureDelegateデリゲートメソッド
extension ViewController: AVCapturePhotoCaptureDelegate {
    // 撮影した画像データが生成されたときに呼び出されるデリゲートメソッド
    func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
    
    //画像のデータを取得し、
        if let imageData = photo.fileDataRepresentation() {
            
            //画像の向きを判定し、修正する向きを設定
            let imageOrientation: UIImage.Orientation = {
                switch getDeviceOrientation(){

                case .down:
                    
                    return .right
                    
                case .right:
                    
                    return .up
                    
                case .left:
                    
                    return .down
                    
                case .up:
                    
                    return .left
                }
            }()

            //向きを考慮して画像に変換
            let image = UIImage(cgImage: UIImage(data: imageData)!.cgImage!,
                                scale: 1.0,
                                orientation: imageOrientation
            )!
	    
	    
	    //できた画像で色々な処理
	    doSomething(image)
	    
        }
    }
}

これで撮った写真も正常な向きになります。

完了

以上で、カメラの表示がおかしくなるのは一応修正出たと思いますが、画面回転時のレイヤーの動きが少し挙動不審なのが気になります...
間違えているところがあったり、もっといい方法があったら教えてください。

参考文献