SwiftUIで画像(UIImage)をJPEG,PNG,HEICで保存する方法

カテゴリー: ,

今回はSwiftUIで画像を書き出して保存する方法について解説します。この記事では、UIImageをドキュメントディレクトリに保存するための関数を実装し、JPEG、PNG、およびHEIC形式での保存方法について説明します。

1. モジュールをインポート

まずは必要なモジュールをインポートします。今回はSwiftUIのみを使用します。

import SwiftUI

2. エラーハンドリング用の列挙型

次に、画像の保存時に発生する可能性のあるエラーを管理するための列挙型を定義し、エラーの種類を特定しやすくします。

enum ImageSaveError: Error {
    case unableToConvertImage
    case unsupportedFormat
}

3. 画像を保存する関数の定義

次に、画像を保存するための関数saveImageToDocumentsDirectoryを定義します。この関数は、画像と保存先のパス、フォーマットを受け取り、指定された形式で画像を保存します。

func saveImageToDocumentsDirectory(image: UIImage, path: URL, format: String, completion: @escaping (Result<Void, ImageSaveError>) -> Void) {
    var data: Data?
    
    switch format.uppercased() {
    //指定された形式ごとに処理を実施します
    }
}
パラメータの説明
  • image: 保存したいUIImageオブジェクトを指定します。
  • path: 保存先のURLを指定します。
  • format: 保存する画像のフォーマット(”JPEG”, “PNG”, “HEIC”)をStringで指定します。
  • completion: 保存処理の結果を非同期に返すためのクロージャ。

3. JPEG形式での保存

JPEG形式の画像に変換し、data変数に代入します。

case "JPEG":
    data = image.jpegData(compressionQuality: 1.0)
compressionQuality

JPEGではcompressionQualityを使用して、JPEG画像の圧縮率を指定します。値は0.0から1.0の範囲で、1.0が最高品質、0.0が最低品質を意味します。
1.0は圧縮なしを意味し、画像の品質が最も高くなりますが、ファイルサイズは大きくなります。
高品質の画像が必要な場合は1.0を使用し、ファイルサイズを抑えたい場合は、品質を調整して低い値を指定することができます。

4. PNG形式での保存

次に、PNG形式で画像を変換する処理を追加します。

case "PNG":
    data = image.pngData()

5. HEIC形式での保存

最後に、HEIC形式での画像の処理を追加します。HEIC形式への変換はiOS 17.0以上でサポートされています。ターゲットOSに合わせて記述を変更してください。iOS17.0以前でサポートするには、UIKitを使用して別のコードを記述する必要があります。

case "HEIC":
    if #available(iOS 17.0, *) {
        data = image.heicData()
    } else {
        completion(.failure(.unsupportedFormat))
        return
    }

6. サポートされないフォーマットが指定された時の処理

クロージャを呼び出し、エラーメッセージを伝えます。

default:
    completion(.failure(.unsupportedFormat))
    return

7. 画像の変換処理結果を確認

変換した画像データをimageData変数に代入し、nilなどのエラーが発生しないことを確認します。

guard let imageData = data else {
    completion(.failure(.unableToConvertImage))
    return
}

8. ディレクトリへの保存

保存が成功したときは成功したことを、失敗した時はエラーをクロージャを通じて知らせます。

do {
    try imageData.write(to: path)
    print("Image saved to documents directory: \(path.absoluteString)")
    completion(.success(()))
} catch {
    print("Error saving image: \(error.localizedDescription)")
    completion(.failure(.unableToConvertImage))
}

全体のコード

以上の部分をまとめると、以下のような関数ができます。

import SwiftUI

enum ImageSaveError: Error {
    case unableToConvertImage
    case unsupportedFormat
}

func saveImageToDocumentsDirectory(image: UIImage, path: URL, format: String, completion: @escaping (Result<Void, ImageSaveError>) -> Void) {
    var data: Data?
    
    switch format.uppercased() {
    case "JPEG":
        data = image.jpegData(compressionQuality: 1.0)
    case "PNG":
        data = image.pngData()
    case "HEIC":
        if #available(iOS 17.0, *) {
            data = image.heicData()
        } else {
            completion(.failure(.unsupportedFormat))
            return
        }
    default:
        completion(.failure(.unsupportedFormat))
        return
    }
    
    guard let imageData = data else {
        completion(.failure(.unableToConvertImage))
        return
    }
    
    do {
        try imageData.write(to: path)
        print("Image saved to documents directory: \(path.absoluteString)")
        completion(.success(()))
    } catch {
        print("Error saving image: \(error.localizedDescription)")
        completion(.failure(.unableToConvertImage))
    }
}

Viewでの使用例

この例では、先ほど作成した関数を使用し、ボタンを押すと画像を保存する処理を行います。


import SwiftUI

struct ContentView: View {
    @State private var selectedFormatIndex = 0
    @State private var image: UIImage? = UIImage(named: "example")
    @State private var saveResult: String = ""

    let formats = ["JPEG", "PNG", "HEIC"]

    var body: some View {
        VStack {
            if let image = image {
                Image(uiImage: image)
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(width: 200, height: 200)
            }

            Picker("Select Format", selection: $selectedFormatIndex) {
                ForEach(0..<formats.count, id: \.self) { index in
                    Text(formats[index])
                }
            }
            .pickerStyle(SegmentedPickerStyle())
            .padding()

            Button(action: {
                if let image = image {
                    saveImage(image: image, format: formats[selectedFormatIndex])
                }
            }) {
                Text("Save Image")
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(8)
            }

            Text(saveResult)
                .padding()
        }
    }

    func saveImage(image: UIImage, format: String) {
        guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
            return
        }

        let fileName = "savedImage.\(format.lowercased())"
        let fileURL = documentsDirectory.appendingPathComponent(fileName)

        saveImageToDocumentsDirectory(image: image, path: fileURL, format: format) { result in
            switch result {
            case .success:
                saveResult = "Image saved successfully!"
            case .failure(let error):
                switch error {
                case .unableToConvertImage:
                    saveResult = "Unable to convert image."
                case .unsupportedFormat:
                    saveResult = "Unsupported format."
                }
            }
        }
    }
}


#Preview {
    ContentView()
}

ファイルAppからファイルを見つけられるようにする

ドキュメントディレクトリに保存したファイルを、ユーザーがファイルAppから開けるようにするには、Xcodeのinfo.plistに以下の値を追加し、YESに設定します。

  • UIFileSharingEnabled (Application supports iTunes file sharing)
  • LSSupportsOpeningDocumentsInPlace (Supports opening documents in place)

まとめ

SwiftUIを使用して画像を指定したフォーマットで保存する方法を解説しました。実際のプロジェクトでこの関数を活用し、ユーザーが画像を保存できるようにしてみてください。
URLを変更することで、サブディレクトリやCachesディレクトリなどに保存することもできます。


サンプルプログラムは、MITライセンスのもとで公開しています。
以下の利用規約を参照の上、使用してください。
ソースコードの利用に関する規約