画像をカメラロールに保存する【Swift5】

はじめに

画像をiPhoneの”写真”アプリのカメラロールに保存する実装をしたいときがあったのですが、単純にUIImageを保存するとEXIF情報や位置情報が失われてしまいました。

本記事では、まず単純にUIImageをカメラロールに保存する手順を説明し、次にEXIF情報などを保持したまま画像を保存する手順について説明しています。

UIImageの保存

カメラロールにUIImageを保存するシンプルな方法は、UIImageWriteToSavedPhotosAlbum(_:_:_:_:)を使用することです。

func UIImageWriteToSavedPhotosAlbum(
        _ image: UIImage,
        _ completionTarget: Any?,
        _ completionSelector: Selector?,
        _ contextInfo: UnsafeMutableRawPointer?
    )

保存するUIImageを引数に取り、カメラロールに保存が成功したときの処理を完了ハンドラに記述できます。非常に簡単です。

JPEG画像データの保存

はじめに書きましたが、画像データをUIImageに変換すると、EXIF情報や位置情報が失われてしまいます。
これらの情報を保持したままカメラロールに保存するには、JPEGデータとして保存する必要があります。

JPEGデータをカメラロールに保存するにはiOS標準のPhotosライブラリを利用します。
以下に画像ファイルをダウンロードしてきてから、カメラロールに保存するまでの一例を示します。
1. “写真”アプリへのアクセス許可を求める
2. 画像データを適当なデータ形式に変換する (EXIFが落ちないように)
3. 変換した画像データをカメラロールに保存する
の流れです。

import Photos
// ...
// 1. "写真"アプリへのアクセス許可を求める
// for引数には.addOnlyと.readWriteを指定できる
PHPhotoLibrary.requestAuthorization(for: .addOnly) { status in
    switch status {
    case .authorized:
        // "写真"へのアクセスが許可されたとき
        // 画像ファイルをダウンロードしてくる処理
        FileDownLoader.shared.download { downloadFile in
            // 2. 画像データを適当なデータ形式に変換する
            // ダウンロードしたファイルをCIImageに変換する
            let ciImage = CIImage(contentsOf: downloadFile)
            // jpegデータに変換
            let jpegData = CIContext().jpegRepresentation(of: ciImage,
                                                          colorSpace: ciImage.colorSpace ?? CGColorSpaceCreateDeviceRGB(),
                                                          options: [:])
            //  3. 変換した画像データをカメラロールに保存する
            PhotoLibrary.shared().performChanges {
                let request = PHAssetCreateRequest.forAsset()
                request.addResource(with: .photo, data: jpegData, option: nil)
            } comlpetionHandler: { success, error in
                if success {
                    // 保存成功
                }
                if let error = error {
                    // エラー
                }
            }
        }
    case .denied:
        // 拒否されたとき
    case .notDetermined:
        // 未決定状態
    case .restricted:
        // ユーザーでは許可できないようなアクセス制御がかかっている場合
    case .limited:
        // 一部の写真を選択できるpresentLimitedLibraryPicker(from:)もしくはpresentLimitedLibraryPicker(from:completionHandler:)を使用する場合
    }
}

⚠️ 上記の例では、ダウンロードしてきたファイルをJPEGデータに変換するために、一度CIImageに変換していますが、CIImageに変換してもEXIF情報などは失われません。

Deprecated対応

上記で使用している”写真”アプリへのアクセスをリクエストするメソッドrequestAuthorization(for:handler:)と似たメソッドとしてrequestAuthorization(_:)がありますが、iOS14以降ではdeprecatedとなっています。
iOS14以降では、requestAuthorization(for:handler:)の使用が推奨されています。
大した違いはなく、for引数に.addOnlyもしくは.readWriteを指定できるようになりました。
これは写真をカメラロールに「追加するのみ」か、カメラロールの写真を「読み込む」かを指定できるようになり、これによって”写真”へのアクセス許可を求めるアラートの中身が変わるようです。
あまり難しく考えず、iOS14以前のバージョンをサポートしている場合は、以下のように記述しておけば問題ないです。コードは冗長になりますが、、、

if #available(iOS14, *) {
        PHPhotoLibrary.requestAuthorization(for: .addOnly) { status in
            // ...
        }
    } else {
        PHPhotoLibrary.requestAuthorization { status in
            // ...
        }
    }

終わりに

本記事では、EXIF情報や位置情報を保持したまま画像データをカメラロールに保存する手順についてまとめました。画像データはさまざまな形で取得してくると思いますので、都度JPEGデータに上手く変換してあげる必要があります。
しかし、 Photosライブラリに用意されているメソッドを使用すれば、ほとんどの場合安全にカメラロールに保存できます🙌

参考文献

この記事は以下の情報を参考にしました。