iPhone / iPadのファイルアプリにテキストファイルを作成する

Swift 4, iOS 12.1で作成

##つくるもの

　アプリ内に書き込んだテキストをテキストファイルとして書き出して、ファイルアプリ内にテキストファイルとして保存します。

<img src="https://qiita-image-store.s3.amazonaws.com/0/93396/6c6ec72f-c9a2-6141-924f-4ef2fc3c7e68.png" width=50%><img src="https://qiita-image-store.s3.amazonaws.com/0/93396/164e2717-45d2-7f5c-e31d-b1bc5ed5dbc3.png" width=50%>

##Info.plistの設定

　<b>Custom iOS Target Properties</b>内のプロパティに項目を追加する。項目を選択するとKeyの右端に+と-が表示されるので、+をクリック。
![スクリーンショット 2019-01-10 18.57.13.png](https://qiita-image-store.s3.amazonaws.com/0/93396/92c084cd-7c10-b24f-1724-8c19c82bba28.png)
　"Supports opening documents in place"と"Application supports iTunes file sharing"を追加し、どちらもValueに"YES"を指定する。
![スクリーンショット 2019-01-10 18.58.51.png](https://qiita-image-store.s3.amazonaws.com/0/93396/cf866880-d64e-6d3a-219c-208a5db959e1.png)
　"Supports opening documents in place"をYESにすると、ファイルアプリに保存できるようになります。
　"Application supports iTunes file sharing"をYESにすると、iTunesからDocumentsフォルダにアクセスできるようになります。

　これらを設定しないと内部では保存された状態になりますが、ファイルアプリ上に表示されません。


##StoryboardでUIの作成

　Main.storyboardにはText FieldとButtonを配置。
![スクリーンショット 2019-01-10 19.23.29.png](https://qiita-image-store.s3.amazonaws.com/0/93396/5adb4391-5a51-43fe-39ff-0de8b1811de4.png)

##ViewControllerと連携

　View ControllerにText FieldとButtonをドラッグ&ドロップして連携。名前はmyTextとsaveButtonにしました。
![スクリーンショット 2019-01-10 19.38.44.png](https://qiita-image-store.s3.amazonaws.com/0/93396/678c8973-26ed-4463-1160-fedbf96c3abe.png)


##処理を書く
処理のフローは以下のようになります：
1. Button(saveButton)をタップしたら以下の処理を行う関数が呼び出される。
2. Text Field(myText)に書かれたテキストを取得。
3. ドキュメントフォルダにテキストファイルを保存。

###saveButtonをタップして関数を呼び出す。

　先ほど置いたsaveButon関数の中に、createTextFile()を実行するよう記入します。

```swift:ViewController.swift
@IBAction func saveButton(_ sender: Any) {
        createTextFile()
    }
```
＊この時点ではエラーが出ますが、まだその関数を置いていないからです。


###createTextFile関数

　ViewControllerクラスの一番下にcreateTextFile関数を作ります。

```swift:ViewController.swift
func createTextFile(){
        let text = myText.text
        let textName = "data.txt"
        
        if let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last {
            let path = dir.appendingPathComponent(textName)
            
            
            do {
                try text?.write(to: path, atomically: true, encoding: String.Encoding.utf8)
            } catch let error as NSError {
                print("エラー：\(error)")
            }
        }
    }
}
```

以下は解説になります。

###変数

　変数にはtextとtextName。関数が呼び出されたときにText Filed(myText)に書かれたテキストを変数textに入れます。書き出すファイル名はtextNameのものを使います。拡張子もここで指定しました。

```swift:ViewController.swift
func createTextFile(){
    let text = myText.text
    let textName = "data.txt"
}
```

###保存場所を指定

　変数宣言に続いて、ドキュメントの場所を探してきます。
　＊上記コードと一部異なっていますが、エラー処理（後述）のためなので、ここでは分かりやすく省略しています。

```swift:ViewController.swift
let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last
let path = dir.appendingPathComponent(textName)
```

　FileManagerクラスはファイルシステムを扱うために使用します。.defaultは常に同じファイルマネージャーを使用するための指定です。.urlsは指定したドメイン内のディレクトリを返してくれます。

　この場合はuserDomainMaskの中のdocumentDirectoryを返してくれます。userDomainMaskはiOSユーザのホームディレクトリを指し、documentDirectoryはドキュメントフォルダのディレクトリです。

　.lastは配列の最後のデータを取り出します。ここで指定した内容で得られるデータは１つだけなのですが、配列から文字列に変換するために.lastもしくは.firstで取り出します。ちなみにオプショナル型になります。

　dir.appendingPathComponentでは括弧内の文字列をdirの最後に追記してくれます。

 つまりdirは
.../Documents/
までのパスを取得しましたが、

dir.appendingPathComponent(textName)で
.../Documents/data.txt
のように追記してくれます。

###テキストファイルの作成
　先ほど取得したテキストファイルのパスへ、テキストを書き込みます。

```swift:ViewController.swift
text?.write(to: path, atomically: true, encoding: String.Encoding.utf8)
```

　text?.writeで指定したパスへtextの文字列を書き込みます。

　text?とアンラップされているのはエラー処理のためです。textはText Fieldに入力された文字列が代入されます。何も書かれずにButtonが押されればnilが返ってきます。!でアンラップすると、nilが返ってきてクラッシュしますが、?でアンラップすることで、それに続く処理が中断されます。

　to:では書き込み先のディレクトリを指定します。先ほど取得したテキストファイルのパスを指定しています。

　atomically:がtrueの場合、一度バックアップファイルに書き込まれ、エラーが発生しなかった場合に、指定したパスに書き込みます。

　encoding:は文字コードの指定です。一般的なUTF-8を指定しました。

###エラー処理

　textFiledに何も書かれていなかったり、ドキュメントのディレクトリが見つからなかったり、書き込みに失敗したり、様々な場合にnilが返ってきてアプリがクラッシュします。クラッシュを避けるためにコードを書き直しました。

```swift:ViewController.swift
        if let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last {
            let path = dir.appendingPathComponent(textName)
            
            do {
                try text?.write(to: path, atomically: true, encoding: String.Encoding.utf8)
            } catch let error as NSError {
                print("エラー：\(error)")
            }
        }
```

　以下は細かく分けて解説です。

```swift:ViewController.swift
        if let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last {
            let path = dir.appendingPathComponent(textName)
        }
```

　ドキュメントフォルダのディレクトリを取得できた場合のみ、テキストファイルの指定をしています。

　またdo-catchとtryでエラーが起きた場合の保険をかけています。

```swift:ViewController.swift
do {
    try text?.write(to: path, atomically: true, encoding: String.Encoding.utf8)
} catch let error as NSError {
    print("エラー：\(error)")
}
```

　do{}内に記述したメソッドでエラーが発生した場合catch{}を実行します。

　do{}内ではメソッドの前にtryを記述します。
　let error as NSErrorで、tryで行ったメソッドのエラー情報がerrorに入り、catch{}内でコンソールに記述させています。


##完成

　ビルドしてみましょう。
　Text Field内に適当な文字を入力してButtonをタップします。

<img src="https://qiita-image-store.s3.amazonaws.com/0/93396/6c6ec72f-c9a2-6141-924f-4ef2fc3c7e68.png" width=50%>


　ホームに戻り、フォルダアプリをタップします。アプリ内にOn My iPhone(iPad)という項目が出現しました。
<img src="https://qiita-image-store.s3.amazonaws.com/0/93396/2ed829d4-d0a8-675f-496b-497616488191.png" width=50%><img src="https://qiita-image-store.s3.amazonaws.com/0/93396/29473acc-5413-10f1-3082-2507b7eb18a3.png" width=50%>


　Browseにプロジェクトファイル名のフォルダができ、中には先ほど生成したdata.txtが生成されました。テキストファイルの中には、Text Fieldに記入したテキストが書かれています。　
<img src="https://qiita-image-store.s3.amazonaws.com/0/93396/bd347f99-6775-ae07-cd92-0edbf634be63.png" width=50%><img src="https://qiita-image-store.s3.amazonaws.com/0/93396/164e2717-45d2-7f5c-e31d-b1bc5ed5dbc3.png" width=50%>