💬

iOS でカメラロールの写真を扱う時のパーミッションの注意点

2023/12/11に公開

こんにちは!アルダグラムでエンジニアをしている渡辺です
本記事は株式会社アルダグラム Advent Calendar 2023 11日目の記事です。

はじめに

iOS ではカメラロールの写真を扱うにはユーザーに対してパーミッションを求める必要があり、ユーザーが許可をしないとカメラロールの写真の利用や保存を行うことができないです。

今回はその中でも PHPickerViewController を利用してカメラロールの写真を扱う時の注意点について書いていきます。

パーミッションの種類

パーミッションは enum で定義されており Apple が提供しているPhotoKit の PHPhotolibrary に定義してある PHAuthorizationStatusPHAccessLevel** を利用します。

PHAuthorizationStatus はカメラロールへのアクセスに対してのパーミッションで5種類のステータスが用意されています。

  • notDetermined: 一度もパーミッションをユーザーが選択していない状態
  • restricted: アプリ自体がアクセスを許可していない状態
  • denied: ユーザーがアクセスを許可していない状態
  • authorized: ユーザーがアクセスを許可している状態
  • limited: ユーザーが選択した写真のみアクセスを許可している状態

PHAccessLevel はカメラロールに対して書き込みや読み込みに対してのパーミッションで2種類のパーミッションが用意されています

  • addOnly: カメラロールへ追加することだけできる
  • readWrite: カメラロールの写真を読み込むことができ、追加することもできる

上記2つのパーミッションを組み合わせてカメラロールの写真の利用やカメラロールへの保存を行うことができるようになります。

パーミッションのリクエストと確認する方法

利用するメソッド

パーミッションのリクエストと確認を行うには これまた Apple が提供しているPhotoKit の PHPhotolibrary を利用します。

PHPhotolibrary にはパーミッションをリクエストするメソッドと現在のパーミッションを取得するメソッドが用意されています。

  • authorizationStatus : 現在のパーミッションを取得します
    • authorizationStatus(for accessLevel: PHAccessLevel) → PHAuthorizationStatus
    • PHAccessLevel は 「カメラロールへ書き込みと読み込む or 読み込みのみ」のパーミッションです
  • requestauthorization : パーミッションをリクエストします
    • requestAuthorization(for accessLevel: PHAccessLevel, handler: @escaping (PHAuthorizationStatus) -> Void)
    • Concurrency で実行する方法も用意されてます
    • リクエストを行うのは初回だけで、2回目以降は現在のパーミッションで handler が実行されます

※ 現在上記2つのメソッドで同じメソッド名で異なる引数のメソッドが Deprecated となっているものがあるので注意してください。

利用方法

ざっくりとしたコードで解説していきます

① 現在の「カメラロールへ書き込みと読み込む」レベルでパーミッションを取得している

② 「カメラロールへ書き込みと読み込む」レベルでパーミッションをリクエストしている

PHPhotoLibrary.requestAuthorization(for: .readWrite)を実行するとユーザーにパーミッションの選択を促すアラートが表示される(初回のみ)

③ 取得したステータスによってお好きな処理を実行していきます

notDeterminedが続く場合は無限にループするがnotDetermined自体が初回のみのステータスで一度でも選択するとnotDeterminedに変わることが無いのと解説用コードなのでこれでいきます

import SwiftUI
import Photos

class SampleCameraRollPermission {
    func getPermission() {
	let status: PHAuthorizationStatus = PHPhotoLibrary.authorizationStatus(for: .readWrite) // ①
	actionsByStatus(status: status) // ③
    }

    func requestPermission() {
	let status = await PHPhotoLibrary.requestAuthorization(for: .readWrite) // ②
	actionsByStatus(status: status) // ③
    }

    private func actionsByStatus(status: PHAuthorizationStatus) {
	switch status {
	    case .notDetermined: // ④
	        // カメラロールへのアクセスが初回の場合
	        await requestPermission()
	    case .denied,
	        // ユーザーがアクセスを拒否した場合の処理
	    case .restricted,
	        // アプリ自体がアクセスを拒否している場合の処理
	    case .limited,
	        // ユーザーが選択した写真のみアクセスを許可している場合の処理
	    case .authorized:
	        // ユーザーがアクセスを許可している場合の処理
	    @unknown default:
	        // デフォルトの処理
        }
    }
}

PHPickerViewController を利用した時の注意点

ここまではカメラロールへのパーミッションの取得方法を書いていきましたが、このパーミッションを利用して PHPickerViewController を利用した時の注意点を書いていきます。

PHPickerViewController はカメラロールの写真を簡単に選択して View などに利用できる UIKit のモジュールで、ImagePicker で選択した時の処理をPHPickerViewControllerDelegate の picker メソッドで配列のPHPickerResultを取得することができます。

この PHPickerResult からPHAssetを取得すると、上記でお話したパーミッションによっては[PHAsset](https://developer.apple.com/documentation/photokit/phasset)が nil になってしまいます 。

[PHAsset](https://developer.apple.com/documentation/photokit/phasset) はメタデータを含んだ状態でカメラロールの写真を利用をしたい場合に使う class となってます。

弊社はメタデータを含んだ状態でサーバーにアップロードするために利用しています

public class SamplePHPickerViewControllerDelegate: PHPickerViewControllerDelegate {      
    public func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
        var assets: [PHAsset] = []
        let assetIdentifiers = results.compactMap { $0.assetIdentifier}
                   // 指定した assetIdentifiers の PHAsset を取得するが
         // パーミッションによってはすべてが nil の配列か、一部が nil となります
         let fetchResults = PHAsset.fetchAssets(withLocalIdentifiers: assetIdentifiers, options: nil)
         fetchResults.enumerateObjects({ asset, _, _ in
	     // asset が nil の場合を考慮する必要がある
             assets.append(asset)
         })
     }
}

パーミッション別 Picker で選択した値から PHAsset 取得した結果

PHAuthorizationStatus

  • notDetermined: アラートで選択したパーミッションによって異なる
  • restricted: アクセスが拒否されているため nil となる
  • denied: アクセスが拒否されているため nil となる
  • authorized: 選択した写真すべての PHAsset が取得できる
  • limited: パーミッションを許可した写真のみ PHAsset が取得できる(許可されていない写真を選択した場合、PHPickerResultは取得できない)

PHAccessLevel

  • addOnly: カメラロールの写真を読み込むことができないため nil となる
  • readWrite: PHAuthorizationStatus が authorized か limited の場合は PHAsset が取得できる

そもそもパーミッションがない場合は PHPickerViewController 側で写真選択ができないのでは?と思った方もいるかも知れませんが、PHPickerViewController はパーミションの有無に関係なく利用ができるモジュールなのでカメラロールの写真がすべて(非表示と削除した写真以外)表示されてしまいます。

そのためステータスが restricted, denied などのカメラロールの写真へアクセスがすべてない場合は Picker を表示する前に制御を入れる必要があります。

また一番厄介なので limited です。このステータスはユーザーが選択した写真のみ利用できるので

パーミッションを許可されていない写真を Picker で選択した場合**PHAsset**が nil となるため「選択した写真はパーミッションが許可されていません」みたいな制御を入れる必要がありますのでご注意ください。

私は選択した写真が何故か反映されないと頭を悩ませ時間を無駄にしてしまった経験がありますのでどなたかのお役に立てれば幸いです。

おまけ

今回はカメラロールの写真を利用する時のことを書きましたが、カメラロールへ保存する場合でも limited ステータスだけ特別な挙動をするので紹介します。

上記でも述べましたが limited はユーザーが選択した写真にアクセスすること許可するパーミッションです。

しかし新たに写真を保存した場合はどうなるのでしょうか…?

そうです。新たにその写真を追加するかどうかのアラートが表示されます。とても親切です。

さらにこのアラートのどちらを選択しても新たに追加した写真は許可されるようになってます。(「その他の写真を追加選択…」の場合はデフォルトで選択済みとして表示されます)

ちなみにこのアラートはタスクキルしてアプリを開くたびにアラートが表示されるようになってます。

補足

パーミッション取得のアラートを表示するためには Info.plist ファイルにPrivacy - Photo Library Additions Usage Descriptionを追加する必要があります。


もっとアルダグラムエンジニア組織を知りたい人、ぜひ下記の情報をチェックしてみてください!

アルダグラム Tech Blog

Discussion