AVCaptureSession.InterruptionReason.videoDeviceNotAvailableInBackground

概要

AVCaptureSession でビデオセッションがバックグラウンド・フォアグラウンドに移行した場合の動作に関して。

Reference

AVCaptureSession.InterruptionReason.videoDeviceNotAvailableInBackground

  • iOS9.0以降
  • Mac Catalyst 13.0+
  • AVFoundation

宣言

case videoDeviceNotAvailableInBackground = 1

Discussion

バックグラウンドでのカメラの使用は禁止されています。バックグラウンドでカメラの実行を開始しようとすると、キャプチャセッションはこの中断理由でAVCaptureSessionWasInterruptedを送信します。stopRunning()メソッドを明示的に呼び出さない場合、startRunning()リクエストは保持され、アプリがフォアグラウンドに戻ると、AVCaptureSessionInterruptionEnded を受信してセッションの実行が開始されます。

解説

AVCaptureがバックグラウンド・バックグラウンドに移行しても、明示的にsession を stopRunning, startRunning を行う必要はなく、自動的に停止・再開される。フォアグランドに戻った時に、AVCaptureSessionInterruptionEnded が呼ばれるので、そこで必要な処理(Permission のチェックなど)を行えば良い。

実装例

    override func viewDidLoad() {
        NotificationCenter.default.addObserver(self, selector: #selector(avCaptureSessionInterruptionEnded), name: Notification.Name.AVCaptureSessionInterruptionEnded, object: nil)
    }

    // フォアグランドに戻って、session が自動的に再開された
    @objc func avCaptureSessionInterruptionEnded() {
        debugPrint("avCaptureSessionInterruptionEnded フォアグラウンドに戻ったので、session が自動的に再開された")
        doSomething()
    }

requestAccess(for:completionHandler:)

オリジナル情報源

このページは、次の情報を日本語に翻訳したものです。お役に立てば幸いです。

requestAccess(for:completionHandler:)

AVFoundation

宣言

必要に応じて、指定されたメディアタイプを記録するためのユーザーの許可を要求します。

class func requestAccess(for mediaType: AVMediaType, 
       completionHandler handler: @escaping (Bool) -> Void)

パラメーター

mediaType

メディアタイプ定数で、videoまたはaudio。メディアタイプが指定されていない場合、invalidArgumentExceptionがスローされます。

handler

許可、または拒否されたときに、1回だけ呼び出されるブロック。

完了ハンドラーは、任意のディスパッチキューで呼び出されます。結果として、UIKit関連の更新がメインキューまたはメインスレッドで呼び出されるようにするのはクライアントの責任です。

ブロックは次のパラメーターを受け取ります。

granted

ユーザーがハードウェアを使用する許可を与えると、true 、それ以外の場合は、falseが返されます。ブロックはすぐに戻ります。

ディスカッション

オーディオまたはビデオの録音には、常にユーザーからの明示的な許可が必要です。許可が必要なメディアタイプのAVCaptureDeviceInputオブジェクトを初めて作成すると、システムは自動的にアラートを表示して記録許可を要求します。または、このメソッドを呼び出して、指定した時間にユーザーにプロンプ​​トを表示することもできます。

重要

アプリは、NSCameraUsageDescriptionまたはNSMicrophoneUsageDescription Info.plist キーを使用してキャプチャデバイスの使用について説明する必要があります。iOSは、最初にユーザーに許可を求めるときにこの説明を表示し、その後、”設定”アプリ に表示します。使用法の説明なしで、このメソッドを呼び出すか、キャプチャセッションを開始しようとすると、例外が発生します。

ユーザーがアクセスを要求されている間、この呼び出しはブロックされず、クライアントは実行を継続できます。アクセスが許可されるまで、メディアタイプのいずれのAVCaptureDevices は、無音オーディオサンプルまたは黒いビデオフレームとなります。ユーザーは、クライアントが最初にアクセスを要求したときにのみ許可を求められます。それ以降の呼び出しでは、ユーザーによって付与されたアクセス許可が使用されます。

ユーザーが録音権限を付与した後、システムは同じアプリで将来使用するための選択を記憶しますが、ユーザーは “設定” アプリを使用していつでもこの選択を変更できます。ユーザーがアプリの再コーディングの許可を拒否した場合、または許可のプロンプトにまだ応答していない場合、音声の録音には無音のみが含まれ、ビデオの録音には黒のフレームのみが含まれます。

このresponseパラメーターは、ユーザーが記録の許可を付与したか拒否したかを示す唯一のパラメーターを持つブロックです。このメソッドは常にすぐに戻ります。ユーザーが以前に記録許可を付与または拒否した場合、呼び出されたときにブロックを実行します。それ以外の場合は、アラートを表示し、ユーザーがアラートに応答した後にのみブロックを実行します。

audioで、このAVAudioSessionメソッドの呼び出しは、requestRecordPermission(_:) メソッドの呼び出しと同じです。

AppClipの対応するアプリでデータを利用できるようにする

リファレンス

このページは、Making Data Available to the App Clip’s Corresponding App を翻訳したものです。

Overview

App Groups 機能を追加し、App Clipに保存されているデータを、対応するアプリで利用できるようにする。

App Clip のユーザーがフルアプリをインストールすると、アプリクリップがフルアプリに置き換わり、すべての呼び出しを受け取ります。アプリクリップに慣れ、フルアプリを使い始めたユーザーに優、れたユーザーエクスペリエンスを提供することは重要です。

例えば:

  • App Clip を使用したときに入力した情報を、アプリで表示します。
  • ダウンロードしたデータをアプリで利用できるようにします。
  • ユーザーは、自分のアカウントに再度ログインする必要がない。

アプリクリップとアプリ間でデータを共有する

App Clip は、共有コンテナにデータを保存することで、対応するアプリがデータにアクセスできるようにすることができます。フルアプリは、App Clip を置き換えるときにこのデータにアクセスすることにより、優れたユーザーエクスペリエンスを提供できます。

共有コンテナにデータを保存するには:

  1. App Clip とフルアプリの両方のターゲットにApp Groups 機能を追加します。
  2. 両方のターゲットに対して、同じ app group capability (たとえば、group.exampleApp.appClipMigration) に追加します。
  3. App Clipのコードで、containerURL(forSecurityApplicationGroupIdentifier:)を使用して共有コンテナーのURLを取得し、そのURLを使用してデータを保存します(たとえばwrite(to:atomically:encoding:))。
  4. アプリのコードで、同じ関数を使用して共有コンテナーのURLを取得し、そのコンテンツにアクセスします。たとえば、init(contentsOf:)を使用します。

共有コンテナに加えて、App Clipは、フルアプリにアクセスできる共有されたUserDefaultsインスタンスに情報を保存することもできます。次のコードは、構成された app group を使用して共有 UserDefaults インスタンスを作成し、文字列を格納します。

guard let sharedUserDefaults = UserDefaults(suiteName: "group.exampleApp.appClipMigration") else {
    // Error handling
}
sharedUserDefaults.set("A sample string", forKey: "sharedText")

ユーザーがフルアプリをインストールすると、共有の user defaults にアクセスできます。たとえば、前のコードに格納されている文字列にアクセスするには、次のようにします。

guard let sharedUserDefaults = UserDefaults(suiteName: "group.exampleApp.appClipDataMigration") else {
    // Error handling}
guard let migratedData = sharedUserDefaults.string(forKey: "sharedText") else { return }

アプリと App Clip 間でユーザーのプライバシーを保護するために、App Clip は対応するアプリとのみデータを共有できます。さらに、App Clip がキーチェーンに保存している情報に、対応するフルアプリからアクセスできるようにすることはできません。

注意

従来の UserDefaults.standard を使用した場合、App Clip とフルアプリ間でのデータ共有はできず、nil が返される。

重要

パスワードなどの機密性の高いユーザー情報を、共有アプリのコンテナーや user defaults に保存しないでください。

Appleでサインイン を推奨

App Clips は、ユーザーが自分のアカウントにサインインしたり、アカウントを作成したりできるようにするために、任意のテクノロジーを使用できます。ただし、App Clipでユーザーがアカウントにログインする必要がある場合は、Appleでサインイン を検討してください。シンプルで安全なプライバシー保護のアカウント作成とサインインエクスペリエンスを提供するだけでなく、ユーザーがフルアプリを使い始めたときにポジティブなエクスペリエンスを提供します。

Appleでサインイン を提供し、共有データコンテナーを作成する場合は、ユーザーがフルアプリの使用を開始するときに再度ログインする必要がないユーザーエクスペリエンスを作成します。次のコードは、ASAuthorizationAppleIDCredentialを共有UserDefaultsインスタンスに格納します。

let groupUserDefaults = UserDefaults(suiteName: "group.com.example.appClipDataMigration")
guard let credential = authorization.credential as ASAuthorizationAppleIDCredential else { return }
groupUserDefaults?.set(credential.user, forKey: "SavedUserID")

アプリのコードで、保存されているApple ID認証資格情報を取得し、それを使用してユーザーにサインインします。

let provider = ASAuthorizationAppleIDProvider()
let groupUserDefaults = UserDefaults(suiteName: "group.com.example.appClipDataMigration")
let user = groupUserDefaults?.get("SavedUserID")
provider.getCredentialState(forUserID: user) { state, error in
    if state == .authorized {
        readFavoriteSmoothies(user)
    }
}

Fix UIImage orientation

References

stackoverflow

Background

It seems UIImage orientation get wrong in some cases.

Solution

import UIKit

extension UIImage {
    
    // Fix UIImage Orientation 
    func fixOrientation() -> UIImage {
        if self.imageOrientation == .up { return self }
        
        UIGraphicsBeginImageContext(self.size)
        self.draw(at: .zero)
        let newImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return newImage!
    }
}

error: The UIDeviceFamily of an App Clip (‘[1, 2]’) must be equal to the UIDeviceFamily of its containing parent app (‘[1]’).

Error Message

error: The UIDeviceFamily of an App Clip (‘[1, 2]’) must be equal to the UIDeviceFamily of its containing parent app (‘[1]’).

Cause

Supported device type of the original application and the App Clip are different.

Original Application
App Clip

Solution

Set same supported device type(s).

Apple should use more appropriate error message!

Error: Terminating app due to uncaught exception ‘NSUnknownKeyException’, reason: ‘[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key view.’

Symtom

While adding App Clip target to the original target, the following uncaught exception occurred. As for the normal app target works fine. Only on App Clip Target did not work.

2021-02-23 05:48:05.968176+0900 NegaViewerAppClip[7319:368124] [Storyboard] Unknown class _TtC10NegaViewer18MainViewController in Interface Builder file.

2021-02-23 05:48:06.032046+0900 NegaViewerAppClip[7319:368124] *** Terminating app due to uncaught exception ‘NSUnknownKeyException’, reason: ‘[<UIViewController 0x7f93b5e20ac0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key imageViewOut.’

*** First throw call stack:

(

0   CoreFoundation                      0x00007fff20421af6 __exceptionPreprocess + 242

)

libc++abi.dylib: terminating with uncaught exception of type NSException

*** Terminating app due to uncaught exception ‘NSUnknownKeyException’, reason: ‘[<UIViewController 0x7f93b5e20ac0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key imageViewOut.’

terminating with uncaught exception of type NSException

CoreSimulator 732.18.6 – Device: iPhone 12 Pro Max (31C0DAD9-EF0A-4822-A496-75B919FEB25E) – Runtime: iOS 14.4 (18D46) – DeviceType: iPhone 12 Pro Max

(lldb) 

Cause

It happened while I was refactoring (renaming) the class.

In the main storyboard, the Module of MainViewController was specified the original application target. So, MainViewController is bundled only for the original application target.

Solution

Check “Inherit Module From Target” as follows and it worked.

[default] [ERROR] Could not create a bookmark: NSError: Cocoa 257 “The file couldn’t be opened because you don’t have permission to view it.”

Environment

Swift
iOS 14.4
On a real iPhone 8 device

Code

   func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
        // close picker
        picker.dismiss(animated: true, completion: nil)
        // check canceled
        if results.count == 0 {
            debugPrint("PHImagePicker was canceled")
            return
        }
        if let itemProvider = results.first?.itemProvider,
           itemProvider.canLoadObject(ofClass: UIImage.self) {
            itemProvider.loadObject(ofClass: UIImage.self, completionHandler: {image, error in
                // do something.
            })
        }
    }

Symptom

When an image is selected by PHPicker, the following error message was reported.

2021-02-20 00:12:24.794146+0900 *** [6290:1858791] [default] [ERROR] Could not create a bookmark: NSError: Cocoa 257 “The file couldn’t be opened because you don’t have permission to view it.” }

Main Thread Checker: UI API called on a background thread: -[UIImageView setImage:]

Error Message #1

Could not create a bookmark: NSError: Cocoa 257 “The file couldn’t be opened because you don’t have permission to view it.”

Solution #1

So far, the code works for me as expected with the message. It seems the error message could be ignored.

Stay tuned with the Apple Developer Forum

Error Message #2

Main Thread Checker: UI API called on a background thread: -[UIImageView setImage:]

Cause #2

imageView has been accessed from the PHPicker completion handler as follows.

itemProvider.loadObject(ofClass: UIImage.self,
completionHandler: {image, error in
                     guard let image = image as? UIImage 

GUI operations should be called in the main thread.

Solution #2

In the completionHandler, image should be handled in the main thread.

   func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
        // close picker
        picker.dismiss(animated: true, completion: nil)
        // check canceled
        if results.count == 0 {
            debugPrint("PHImagePicker was canceled")
            return
        }
        if let itemProvider = results.first?.itemProvider,
           itemProvider.canLoadObject(ofClass: UIImage.self) {
            itemProvider.loadObject(ofClass: UIImage.self, completionHandler: {image, error in
                DispatchQueue.main.async {
                    guard let image = image as? UIImage else {
                        debugPrint("Error: UIImage is nil")
                        return }
                    self.imageView.image = image
                }
            })
        }
    }


Cannot initialize kernel with given library data.

Error

2021-02-19 10:12:59.279429+0900 **** [58937:5072989] [api] +[CIKernel kernelWithFunctionName:fromMetalLibraryData:options:error:] Cannot initialize kernel with given library data.

Fatal error: ‘try!’ expression unexpectedly raised an error: Error Domain=CIKernel Code=6 “(null)” UserInfo={CINonLocalizedDescriptionKey=Cannot initialize kernel with given library data.}: file ****/MyFilter.swift, line 11

2021-02-19 10:12:59.280439+0900 ****[58937:5072989] Fatal error: ‘try!’ expression unexpectedly raised an error: Error Domain=CIKernel Code=6 “(null)” UserInfo={CINonLocalizedDescriptionKey=Cannot initialize kernel with given library data.}: file ****/MyFilter.swift, line 11

Cause

The Metal Compiler and Linker flags have not been set.

Solution

Add the following -fcikernel flags to the Build Settings.