【Swift5】アプリで画像を保存する方法【ローカル編】

## はじめに
この記事では、ユーザーのカメラロールなどから読み込んだ画像をローカルに保存したり呼び出したりする方法を解説していきます。
本記事では FileSystem を用いた保存を実装するので、UserDefaultsでは容量オーバーで保存できなかった画像も保存する事ができるようになります。(UserDefaultsで保存することができるのは8MBまで)
そのため、アプリの背景画像などにユーザーの写真を使用したりアイコン画像の設定などを行えるようにする際にとても便利な方法です。

### 環境
・macOS catalina version 10.15.7
・Xcode version 12.2
・swift 5.3.1

## 概要
今回紹介する方法では、以下の3つ(＋2つ)の関数を定義して画像の保存と読み出しを実装します。

1. store関数：画像ファイルの書き込み(保存)を行う
2. retrieveImage関数：画像ファイルの読み出しを行う
3. filePath関数：書き込みや読み出しの際に使うファイルパスの作成を行う
4. save関数：エラーを防ぎつつstore関数を呼び出すための関数 (任意)
5. display関数：エラーを防ぎつつretrieveImage関数を呼び出すための関数 (任意)

###1. store関数の定義
store関数は保存したい画像を引数にとり、その画像をUserDefaultローカルに保存する関数です。
画像保存をする処理を行いたいクラス内に以下のように宣言てして、使いたいタイミングで呼び出して使う事ができます。



```php5
//store関数の定義
func store(image: UIImage, forKey key: String) { 
       if let pngRepresentation = image.pngData() {
           if let filePath = filePath(forKey: key) {
               do  {
                   try pngRepresentation.write(to: filePath, options: .atomic)
               } catch let error {
                   print(error)
               }
           }
       }
   }
```
<br>
###2. retrieveImage関数の定義

retrieveImage関数はstore関数を用いて保存した画像を読み出すための関数です。
store関数で保存する際に自由に設定したkey(String型)をとり、対応する画像を読み出します。

```php5
//retrieveImage関数の定義
func retrieveImage(forKey key: String) -> UIImage? {
       if let filePath = self.filePath(forKey: key), let fileData = FileManager.default.contents(atPath: filePath.path), let image = UIImage(data: fileData) {
           return image
       }
       return nil
   }
```
<br>

###3. filePath関数
filePath関数はsotre関数やretrieveImage関数で画像の保存や読み出しを行う際に、画像の保存場所を決めるために必要な処理を行う関数です。返り値として画像のURLを返してくれます。
こちらもstore関数やretrieveImage関数と同じクラス内で宣言します。

```php5
//filePath関数
func filePath(forKey key: String) -> URL? {
        let fileManager = FileManager.default
        guard let documentURL = fileManager.urls(for: .documentDirectory, in: FileManager.SearchPathDomainMask.userDomainMask).first else { return nil }
        return documentURL.appendingPathComponent(key + ".png")
    }
```
<br>

###4. save関数
save関数はわざわざ宣言しなくても画像の保存は行えますが、処理の順番によるエラーなどを防ぐために宣言してsave関数で保存を行うととても便利です。
以下のコードは同時に最大一枚までの画像を保存するコードになっています。
以下でselectedImageは保存したい画像(UIImage)です。UIImagePickerControllerなどで取得したユーザーの画像をselectedImageに代入するようにしてください。
また、この関数も他の関数同様に保存や読み出しを行いたいクラス内で宣言してください。

```php5
//save関数の宣言
@objc func save() {
        if let imageForImage = selectedImage {
            DispatchQueue.global(qos: .background).async {
                self.store(image: imageForImage, forKey: "Image")
            }
        }
    }
```
<br>

###5. display関数の定義
こちらもsave関数と同様に宣言しなくても問題はないのですが、エラーを防ぐために宣言して用いると便利です。こちらも同時に一枚までの画像を保存＆取得する際のコードを示しました。
以下で、imageView は読み出した画像を表示するためのUIImageViewです。

```php5
//display関数の宣言
@objc func display() {
        DispatchQueue.global(qos: .background).async {
            if let savedImage = self.retrieveImage(forKey: "Image") {
                DispatchQueue.main.async {
                    imageView.image = savedImage
                }
            }
        }
    }
```
<br>

##まとめ
以下に今回紹介紹介したコードをまとめました。
実際に画像の保存や読み出しを行う際は、以下の関数の定義を行った上で、適宜save関数やdisplay関数などを処理を行いたいタイミングで呼び出して使用してください。

```php5
//画像保存＆読み出しを行うクラス内

    func store(image: UIImage, forKey key: String) {
       if let pngRepresentation = image.pngData() {
           
           if let filePath = filePath(forKey: key) {
               do  {
                   try pngRepresentation.write(to: filePath, options: .atomic)
               } catch let error {
                   print(error)
               }
           }
       }
   }
       
    func retrieveImage(forKey key: String) -> UIImage? {
       if let filePath = self.filePath(forKey: key), let fileData = FileManager.default.contents(atPath: filePath.path), let image = UIImage(data: fileData) {
           return image
       }
       return nil
   }
    
    func filePath(forKey key: String) -> URL? {
        let fileManager = FileManager.default
        guard let documentURL = fileManager.urls(for: .documentDirectory, in: FileManager.SearchPathDomainMask.userDomainMask).first else { return nil }
        return documentURL.appendingPathComponent(key + ".png")
    }
       
    @objc func save() {
        if let imageForImage = selectedImage {
            DispatchQueue.global(qos: .background).async {
                self.store(image: imageForImage, forKey: "Image")
            }
        }
    }
    
    @objc func display() {
        DispatchQueue.global(qos: .background).async {
            if let savedImage = self.retrieveImage(forKey: "Image") {
                DispatchQueue.main.async {
                    imageView.image = savedImage
                }
            }
        }
    }

```
<br>


##参考にしたページ

https://developer.apple.com/documentation/foundation/data/1779858-write
https://programmingwithswift.com/save-images-locally-with-swift-5/
<br>

## 自作アプリ
私たちは、自作のリマインダーアプリ「タスクリマインダー -TaskReminder 課題管理-」、決断代行アプリ「決断メーカー」をAppStoreにて公開しています。

ダウンロードはこちらから
・ [タスクリマインダー -TaskReminder 課題管理-](https://apple.co/3drJswx "タスクリマインダー -TaskReminder 課題管理- App Store")
・ [決断メーカー](https://apple.co/2R38PgL "決断メーカー App Store")
<br>