(2022/01/18 更新)
アプリ内のファイル読み書き方法を解説します。
環境
この記事の情報は次のバージョンで動作確認しています。
【Swift】5.3.2
【iOS】14.4
【macOS】Big Sur バージョン 11.1
iOSアプリのファイルシステム
iOSアプリは、システム(iOS)が不正に操作されるのを防ぐ為、アクセスできるフォルダがアプリ毎に隔離されています。(Sandbox構造)
対象のフォルダは、該当アプリからのみアクセスできますが、端末からアプリを削除すると一緒に削除されます。
本記事では、対象フォルダでのファイル読み書きについて解説します。
なお、アプリに元からバンドルされているリソースファイルにアクセスする方法は、【Swift】プロジェクト内のファイル読み込み を参照して下さい。
iOSアプリのフォルダ構成
こちらがiOSアプリから読み書きできるフォルダの構成例です。
(フォルダの開き方は、後で解説します)
Documents
ユーザーが作成したファイル、あるいはアプリケーションで再作成できないドキュメントやその他データ(画像、動画など)のうち、ユーザーに見えるようにするファイルをここに保存します。
このフォルダの内容は、後述する方法によって「ファイル」アプリケーションで参照できます。
任意のサブフォルダを作成可能です。
このフォルダはiTunes/iCloudにバックアップされます。
上記に該当しないファイル(キャッシュデータなど)を置いた場合、AppStoreの審査でリジェクトになる場合があります。
Library
Documentフォルダに入れるようなデータ以外を保存するフォルダです。
UserDefaultsに保存したデータは、plistとしてこのフォルダ配下に保存されます。
このフォルダ配下に置いたファイルの内、Chacheフォルダ以外はiTunes/iCloudにバックアップされます。
Library/Application Support
アプリに必要であるが、アプリのデータベースファイルのようにユーザーに見えないようにするファイルをここに保存します。
Core DataのDBファイルもここに作成されます。
このフォルダはiTunes/iCloudにバックアップされます。
Library/Caches
再度ダウンロードまたは再生成できるデータを保存するフォルダです。
例として、データベースのキャッシュファイルや、雑誌、新聞、地図のアプリケーションで使用されるようなダウンロード可能なコンテンツなどが含まれます。
デバイスのディスクスペースが不足している場合、iOSはしばらく使用されていないアプリのキャッシュファイルを削除する可能性があります。
このフォルダはiTunes/iCloudにバックアップされません。
tmp
一時的にのみ使用されるデータを保存するフォルダです。
ファイルを使い終わったら削除をお勧めします。
アプリが起動していない時に、システムによって自動削除される可能性があります。
このフォルダはiTunes/iCloudにバックアップされません。
アプリからファイルの読み書きをする
はじめにサンプルソースを示します。
「実行」ボタンを押すと、アプリケーションのDocumentsフォルダ内の"output.txt"ファイルに文字列を書き込みをし、その後同ファイルから内容を読み込むサンプルです。
実行ボタンはSwiftUIで実現していますが、ファイルの書き込みおよび読み込み処理にはSwiftUIの機能は使用していませんので、UIKitを使ったアプリでも流用可能です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
import SwiftUI struct ContentView: View { var body: some View { Button("実行") { /// ファイル書き込み self.writingToFile(text: "カピ通信") /// ファイル読み込み /// 読み込んだファイルの内容を確認 print("【ファイル内容】\(self.readFromFile())") } } /// ファイル書き込みサンプル func writingToFile(text: String) { /// ①DocumentsフォルダURL取得 guard let dirURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { fatalError("フォルダURL取得エラー") } /// ②対象のファイルURL取得 let fileURL = dirURL.appendingPathComponent("output.txt") /// ③ファイルの書き込み do { try text.write(to: fileURL, atomically: true, encoding: .utf8) } catch { print("Error: \(error)") } } /// ファイル読み込みサンプル func readFromFile() -> String { /// ①DocumentsフォルダURL取得 guard let dirURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { fatalError("フォルダURL取得エラー") } /// ②対象のファイルURL取得 let fileURL = dirURL.appendingPathComponent("output.txt") /// ③ファイルの読み込み guard let fileContents = try? String(contentsOf: fileURL) else { fatalError("ファイル読み込みエラー") } /// ④読み込んだ内容を戻り値として返す return fileContents } } |
ファイル書き込み処理の解説
DocumentsフォルダURL取得
FileManager.default.urls()を使って、DocumentsフォルダURLを取得します。
1 2 3 4 5 6 |
/// ①DocumentsフォルダURL取得 guard let dirURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { fatalError("フォルダURL取得エラー") } |
引数(for)にはDocumentフォルダを示す.documentDirectoryを、引数(in)にはアプリのファイルが格納されているフォルダドメインを示す.userDomainMaskを指定します。
戻り値にはフォルダURLの配列が返るので、.firstをつけて先頭[0]の要素を取得します。
対象フォルダが取得できない場合はnilが戻りますので、その場合サンプルではfatalError()で強制的にアプリをクラッシュさせています。
FileManagerはファイルとディレクトリを管理するクラスです。
このクラスの1つのオブジェクトがアプリに割り当てられ、アプリのために予約されたストレージ スペース内のファイルやディレクトリを作成、削除、コピー、移動ができます。
このオブジェクトを参照するプロパティがFileManager.defaultです。
対象のファイルURL取得
フォルダURLにファイル名を追加して、ファイルURLとします。
ファイルに対しての操作は、このファイルURLを使用します。
1 2 3 4 |
/// ②対象のファイルURL取得 let fileURL = dirURL.appendingPathComponent("output.txt") |
ファイルの書き込み
Stringに標準で用意されているwriteメソッドを使って、ファイルに書き込みを行います。
1 2 3 4 5 6 7 8 |
/// ③ファイルの書き込み do { try text.write(to: fileURL, atomically: true, encoding: .utf8) } catch { print("Error: \(error)") } |
引数(to)には書き込むファイルのURLを指定します。ファイルが存在しない場合は、生成されます。
引数(atomically)は、元のファイルが破損しないようにするために、データを最初に補助ファイルに保存するかどうかをBool値で指定します (true推奨)。
引数(encoding)は、文字列を生成するために使用されるエンコーディングタイプを指定します。
特別な事情が無い場合は、utf8にしておけば問題ありません。
このメソッドは、書き込みできない場合にエラーを返す為、do-try-catchでエラーハンドリングをしています。
ファイル読み込み処理の解説
①DocumentsフォルダURL取得、②対象のファイルURL取得はファイル書き込み処理と同様です。
ファイルの読み込み
特別なイニシャライザであるString(contentsOf :)を使用して、ファイルの内容を文字列にロードします。
このイニシャライザはロードできない場合にエラーを返す為、tryまたはtry?を使用して呼び出す必要があります。
1 2 3 4 5 6 |
/// ③ファイルの読み込み guard let fileContents = try? String(contentsOf: fileURL) else { fatalError("ファイル読み込みエラー") } |
fileContentsへの読み込みが成功したら、以降は通常の文字列として好きなように使用できます。
実際のファイルを見る方法
アプリ内のフォルダにアクセスしてファイルを見る方法を3つ紹介します。
実機アプリのフォルダを開く方法
Xcodeのメニューから Windows>Devices and Simulators を選択し、次の画面を開きます。
- Devicesを選択
- 対象の端末を選択
- 対象のアプリを選択
- 歯車アイコンをクリック
次のメニューが開くので、Download Containerを選択し、フォルダコンテナをMacにダウンロードします。
ダウンロードしたファイルを右クリックし「パッケージの内容を表示」するとフォルダの内容が確認できます。
シミュレータのフォルダを開く方法
現行のXcodeからはなぜかシミュレータのフォルダを開けないので、Finderで直接アクセスします。
シミュレーターのデータは、/Users/${USER}/Library/Developer/CoreSimulator/Devices/ にあります。
※Libraryは隠しフォルダになっていますので、Cmd + Shift + .(ピリオド)で表示させます。
各シミュレーターのフォルダ名はID形式になっています。またシミュレーター内の各アプリフォルダもID形式になっており、どれが対象のフォルダか容易にはわかりません。
ファイルの変更日である程度推測はできますが、確実なのはアプリに次のコードを埋め込んでフォルダのパスを表示させる方法です。
1 2 3 4 |
let documentPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) print(documentPath) |
「ファイル」アプリからフォルダを開く方法
iOS11以降で搭載されたiOS標準の「ファイル」アプリを使うと、アプリのDocumentsフォルダにあるファイルを直接閲覧、編集、コピー等できます。
「ファイル」アプリからのアクセスを許可するには、info.plistの設定が必要です。
info.plistの設定
- Project navigatorで info.plist クリック
- 次の2行を追加し、Valueを YES にする
- Application supports iTunes file sharing
- Supports opening documents in place
※マウスポインタを適当なKey名の所に持っていくと画面のように、Key名の右側に「+(プラス)」と「ー(マイナス)」ボタンが現れるので、「+」をクリックで行を追加できます。
設定後アプリを再buildすると、ファイルアプリからアクセス可能になります。
ファイルアプリからのアクセス手順
- ファイルアプリをタップ
- タブメニューの「ブラウズ」をタップ
- 「このiPhone内」をタップ
- アクセス可能なアプリ一覧が表示される。対象アプリをタップ
- Documentsフォルダのファイル一覧が表示される。ファイルをタップ。
- ファイルの内容が表示される。
Xcode13.X以上の注意点
Xcode13から新規プロジェクト作成時にinfo.plistが生成されなくなりました。
代わりに、下記画面のinfoタブで同様の設定が行えます。