ぼやき雑記

とあるフリーランスプログラマのぼやきです。

Swift4でiOS11カメラアプリを作った時につまづいた点 : カメラプレビューサイズ

趣味案件なのですが、iOSでカメラアプリを作っています。
その時に大きくつまづいてしまった点について、記述したいと思います。
具体的にはitunes connectsで何度も以下のように怒られる点です。

Guideline 2.4.1 - Performance - Hardware Compatibility


We noticed that your app crashed on iPad running iOS 11.1.

Next Steps

To resolve this issue, please revise your app to ensure it runs as expected and displays properly at iPhone resolution on iPad. Even if your app was developed specifically for iPhone, users should still be able to use your app on iPad.

Resources

For information on iOS device screen sizes and resolutions, please review the iOS Human Interface Guidelines as well as Points versus Pixels in the View Programming Guide for iOS.

You may also want to view Size Classes and Core Components for more information about designing apps for multiple screen sizes.

参考にした記事

こちらの記事を参考にしました。
qiita.com

修正点

何につまづいたか

上記itunes connectsのコメントを意訳すると、「iPadサイズだと画面が崩れるので直してください」だと思います。
最初、crashedと書いてあったのでクラッシュ原因を探っていたのですが、どうやら画面の崩れをクラッシュしていると表現しているっぽいです。

どこを直したか

ざっくりソースコード

必要な部分だけ切り出して紹介。
多くの部分は、上記のQiita記事を真似しています。

    var captureSesssion: AVCaptureSession!
    var stillImageOutput: AVCapturePhotoOutput?
    var previewLayer: AVCaptureVideoPreviewLayer?
    
    @IBAction func takeIt(_ sender: UIButton) {
        let settingsForMonitoring = AVCapturePhotoSettings()
        settingsForMonitoring.flashMode = .auto
        settingsForMonitoring.isAutoStillImageStabilizationEnabled = true
        settingsForMonitoring.isHighResolutionPhotoEnabled = false
        // シャッターを切る
        stillImageOutput?.capturePhoto(with: settingsForMonitoring, delegate: self)
    }
    
    override func viewWillAppear(_ animated: Bool) {
        
        captureSesssion = AVCaptureSession()
        stillImageOutput = AVCapturePhotoOutput()
        
        captureSesssion.sessionPreset = AVCaptureSession.Preset.hd1920x1080 // 解像度の設定
        
        let device = AVCaptureDevice.default(for: AVMediaType.video)
        do {
            let input = try AVCaptureDeviceInput(device: device!)
            
            // 入力
            if (captureSesssion.canAddInput(input)) {
                captureSesssion.addInput(input)
                
                // 出力
                if (captureSesssion.canAddOutput(stillImageOutput!)) {
                    captureSesssion.addOutput(stillImageOutput!)
                    captureSesssion.startRunning() // カメラ起動
                    
                    do {
                        try device?.lockForConfiguration()
                        let zoomFactor:CGFloat = 8
                        device?.videoZoomFactor = zoomFactor
                        device?.unlockForConfiguration()
                    } catch {
                        //Catch error from lockForConfiguration
                    }
                    
                    previewLayer = AVCaptureVideoPreviewLayer(session: captureSesssion)
                    //previewLayer?.videoGravity = AVLayerVideoGravityResizeAspect // アスペクトフィット
                    previewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
                    previewLayer?.connection?.videoOrientation = AVCaptureVideoOrientation.portrait // カメラの向き
                    
                    cameraView.layer.addSublayer(previewLayer!)
                    
                    // ビューのサイズの調整
                    previewLayer?.frame = cameraView.bounds
                }
            }
        }
        catch {
            print(error)
        }
    }
    override func viewDidAppear(_ animated: Bool) {
        previewLayer?.frame = cameraView.bounds
    }
違いは何か

この部分を追記しました。

    override func viewDidAppear(_ animated: Bool) {
        previewLayer?.frame = cameraView.bounds
    }
追記した理由

元々の問題はiPadでの画面崩れが問題でした。その理由はAutoLayoutで、カメラプレビュー部分のサイズを変更している点でした。
ソースコードで言うcameraViewですね。

つまり、cameraViewのサイズがAutoLayoutで変更されて、画面サイズにぴったりになるように変形した後のサイズはどのタイミングという問題になります。


そこで、以下の関数が呼び出されるタイミング

  • viewWillAppear
  • viewDidAppear

viewWillAppear はViewが配置される直前、viewDidAppear はViewが配置される直後となります。Viewが配置されるタイミングでAutoLayoutでcameraViewサイズが決まるなら、その後のタイミングで画面サイズに合わせないといけなかったですね。
そのためviewDidAppearを追記しました。

* 最後に
今回、Swiftを初めて触る機会に恵まれ、このような勉強をさせていただきました。
業務でもSwiftを使ったアプリが作れるよう、今後とも精進したいと思います。