【Swift4】コピペで出来るDocumentsフォルダの操作方法(画像の保存編)


はじめに

IOSアプリを開発していて、簡単なユーザー情報であればDBを介さずに"UserDefaults"もしくは"documentDirectory"を利用するのがセオリーらしいので簡単にまとめてみました。
今回はDocuments配下に画像データを保存してみます。

手順は以下になります。

①「画像をDocuments下のフォルダに保存する」
②「Documents下のパス情報をUserDefaultsに保存する」
③「画像を読み込む際に②で保持したパス情報をUserDefaultsから参照して読み込み」

swiftではディレクトリ・ファイルの操作にはFileManagerクラスを使用します。

今回は画像データの保存から読み込みの紹介のみになるのでその他のファイル関係の操作が必要になったら、FileManagerクラスに適当なメソッドが無いか公式ドキュメントをご確認ください。FileManager

情報が不足している箇所や誤り等があれば是非愛情を込めたコメントをいただけると幸いです。

ファイルシステムの基礎について

documentDirectoryを理解するためには、ファイルシステムとは何かを理解する必要があります。詳細はここで確認できます。ファイルとディレクトリの概要

ファイルシステムは、どのオペレーティングシステムにおいても重要な部分を占めます。各ユーザーがコンテンツを保管する場所であるためです。ファイルシステムの構成は、ユーザーのファイルの検索を支援する上で重要な役割を果たします。またアプリケーションおよびシステム自体についても、ユーザーのサポートに必要になるリソースの検出とアクセスが、ファイルシステムの構成により効率化します。

「ある一定期間変更がなく、わざわざデータベースから参照するまでもない情報」を扱う時に僕は利用しています。(ユーザー情報とか)

ディレクトリ構造は以下のようになっています。

「Document」、「Library」、「Temp」3つのフォルダが存在しています。
各フォルダで保存するファイルが変わってくるので注意が必要です。
また、どこのフォルダに保存をするかをガイドラインに従ってわけなければリリース申請でリジェクトされてしまうので気を付けましょう。iOS Data Storage Guidelines

Documents/

ユーザーが生成したデータを保存するために使います。ファイル共有の機能により、ユーザーはこのディレクトリ以下にアクセスできます。したがって、ユーザーに見せても構わないファイルのみ置いてください。
iTunesおよびiCloudはこのディレクトリの内容をバックアップします。

Library/

これは、ユーザーのデータファイル以外のファイル用の最上位ディレクトリです。通常、標準的なサブディレクトリを用意し、いずれか適当な場所に保存します。iOSアプリケーションは通常、Application SupportおよびCachesというサブディレクトリを使いますが、独自のサブディレクトリを作成しても構いません。

ユーザーに見せたくないファイルはLibraryサブディレクトリ以下に置いてください。ユーザーデータのファイル保存用に使ってはなりません。

Libraryディレクトリの内容はiTunesおよびiCloudによってバックアップされます(ただし、Cachesサブディレクトリは除く)。

tmp/

このディレクトリは、アプリケーションを次に起動するまで保持する必要のない一時ファイルを書き込むために使用します。不要になったファイルは削除しなければなりません。もっとも、アプリケーションが動作していないときに、システムがこのディレクトリ以下をすべて消去することがあります。
iTunesまたはiCloudはこのディレクトリの内容をバックアップしません。

各ディレクトリのパス取得方法

Documentsのパス
let DocumentsPath = NSHomeDirectory() + "/Documents"
//.lastではなく[0]の場合、非Optionalな結果を取得できます。
let DocumentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
Libraryのパス
let LibraryPath = NSHomeDirectory() + "/Library"
//.lastではなく[0]の場合、非Optionalな結果を取得できます。
let LibraryPath = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true)[0]
tmpのパス
let tmpDirectory = NSHomeDirectory() + "/tmp"
let tmpDirectory = NSTemporaryDirectory()

画像データの保存 → 保存先のパスを保存 → 読み込み

それでは実際に、画像をDocuments下のフォルダに保存してみましょう。
最近のApple製APIのトレンドではファイル処理の場合でもパス(String)ではなくURLを渡すというのが主流らしいので、そちらの書き方でご紹介します。
(というか確かString型のfileWithPathのメソッドがSwiftにないので、String型→URL型に変換するしかなかった記憶が・・。)

パスとファイルURLの違いに関してはこちらの記事で解りやすくまとめられています。
パスとファイルURLの違いと相互変換の方法

それでは機能実装のための主幹となる部分をざっと説明をします。

①「画像をDocuments下のフォルダに保存する」

画像データをDocuments下のフォルダに保存

var documentDirectoryFileURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]

②「Documents下のパス情報をUserDefaultsに保存する」

保存先のパスをUserDefaultsに保存
userDefaults.set(documentDirectoryFileURL, forKey: "userImage")

③「画像を読み込む際に②で保持したパス情報をUserDefaultsから参照して読み込む」

保存先をパス指定して画像データを読み込む
 //(String型のfileWithPathのメソッドがSwiftにないので、一度URL型に変更)
 let fileURL = URL(fileURLWithPath: filePath).appendingPathComponent("localData.png")
 //.pathプロパティを引数に画像読み込み
 let uiImage = UIImage(contentsOfFile: fileURL.path)
//任意の変数を代入して出力する。
 imageView.image = uiImage

まとめます。

まとめコード

    //UserDefaults のインスタンス生成
    let userDefaults = UserDefaults.standard

    // ドキュメントディレクトリの「ファイルURL」(URL型)定義
    var documentDirectoryFileURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]

    // ドキュメントディレクトリの「パス」(String型)定義
    let filePath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]

   //②保存するためのパスを作成する
    func createLocalDataFile() {
        // 作成するテキストファイルの名前
        let fileName = "localData.png"

        // DocumentディレクトリのfileURLを取得
        if documentDirectoryFileURL != nil {
            // ディレクトリのパスにファイル名をつなげてファイルのフルパスを作る
            let path = documentDirectoryFileURL.appendingPathComponent(fileName)
            documentDirectoryFileURL = path
        }
    }


    //画像を保存する関数の部分
    func saveImage() {
        createLocalDataFile()
        //pngで保存する場合
        let pngImageData = UIImagePNGRepresentation(imageView.image!)
        do {
            try pngImageData!.write(to: documentDirectoryFileURL)
            //②「Documents下のパス情報をUserDefaultsに保存する」
            userDefaults.set(documentDirectoryFileURL, forKey: "userImage")
        } catch {
            //エラー処理
            print("エラー")
        }
    }


       // ③UserDefaultsの情報を参照してpath指定に使う
        let path = String(describing: UserDefaults.standard.url(forKey: "userImage"))
        if let image = UIImage(contentsOfFile: fileURL.path) {
            let image = UIImageView(image: image)
                imageView.image = image.image
                imageView.contentMode = UIViewContentMode.scaleAspectFill
            print("指定されたファイルが見つかりました")
        }else{
            print("指定されたファイルが見つかりません")
        }

以上になります。
もし途中で実際にアプリ内のdocumentに正しく保存されているのか確認したければ、

iPhoneをMacに繋いだ状態で
Xcodeの「Window」 → 「Devices and Simulator」を選択します。

左ペインの「DEVICES」で自分のiPhoneを選択します。「INSTALLED APPS」から実行したアプリを選択し、下の設定ボタンを押下。

「Download Container」で適当な場所にファイルをダウンロードします。

ダウンロードしたファイルを右クリックし、「パッケージの内容を表示」を選択します。
そこでアプリ内のファイル格納ディレクトリがあるので「Documents」まで行くと、

画像データの確認ができました。

以上となります。
それではみなさん良いお年を。

参考にした記事のまとめ。

ファイルを保存してアプリ内で出力してみた
Documentsフォルダ内の画像ファイル読み書き
パスとファイルURLの違いと相互変換の方法
【Swift】Tips: あると便利だったextension達(UIImage編)