メモ保存~swiftUI備忘録~

今回はSwiftUIでは以下のようなiphoneユーザならよく目にする保存UIをコードを追って作っていきます。
SwiftUIは開発者ライクと言われていますが、深堀して行くとやはり難しいですね

このUI機能の仕様

1.ユーザーが入力できるテキスト部分
2.保存機能
3.読み込みボタン

笑っちゃうくらい単純な機能ですね。

![スクリーンショット 2021-06-13 16.06.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/826735/d5845dc2-70bf-4f3e-7b82-4c0fdfce69fa.png)



#全体のコード

```swift
//
//  ContentView.swift
//  Shared
//
//  Created by 市川マサル on 2021/06/12.
//

import SwiftUI
//キーボードを下げる
extension UIApplication {//UIApplicationクラス拡張してキーボードを下げる。
    func endEditing() {
        sendAction(
            #selector(UIResponder.resignFirstResponder),
            to:nil,from:nil,for:nil
        )
    }
}

struct ContentView: View {
    @State var theText: String = ""
    var body: some View {
        NavigationView {
            TextEditor(text: $theText)
                .lineSpacing(10)
                .border(Color.gray)
                .padding([.leading,.bottom,.trailing])
                .navigationTitle("メモ")
                .toolbar{
                    //読み込みボタン
                    ToolbarItem(placement: .navigationBarTrailing) {
                        Button {
                            if let data  = loadText("sample.txt") {
                                theText = data
                            }
                            } label: {
                                Text("読み込み")
                            }
                    }
                    //保存ボタン
                    ToolbarItem(placement: .navigationBarTrailing) {
                        Button {
                            UIApplication.shared.endEditing()//キーボードを下げる
                            saveText(theText, "sample.txt")//ユーザー定義関数のsaveText()テキストデータを保存する
                        } label: {
                            Text("保存")
                        }
                    }
                    
                }
        }
    }
    
    func saveText(_ textData:String, _ fileName:String){
        //docURLはユーザー定義変数
        guard let url = docURL(fileName) else {
            return
        }
        //ファイルパスへの保存
        do {
            let path = url.path
            try textData.write(toFile: path, atomically: true, encoding:  .utf8)
        } catch let error as NSError {
            print(error)
        }
    }
    
    func docURL(_ fileName:String) -> URL? {
        let fileManager = FileManager.default
        do {
            //Documentsフォルダ
            let docsUrl = try fileManager.url(
                //Documentフォルダを指定する。
                for: .documentDirectory,
                in: .userDomainMask,
                appropriateFor: nil,
                create: false)
            let url = docsUrl.appendingPathComponent(fileName)
            return url
        }catch {
            return nil
        }
    }
    
    //テキストデータを読み込んで返す。
    func loadText(_ fileName:String) -> String? {
        //URLを得られたらアンラップしてurlに代入
        guard let url = docURL(fileName) else {
            return nil
        }
        //urlからの読み込み
        do {
            let textData = try String(contentsOf: url, encoding: .utf8)
            return textData
        } catch {
            return nil
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}



```



ひとつづつ説明していきます。



#1.キーボードを下げるコード

appleから提供している便利なコードですこれを実装して呼び出すだけで下げてくれます。大変便利ですし汎用性も高いんではないでしょうか??
呼び出し先としてはテキストの.toolbar内に記述します。toolbarの保存ボタンを押すと閉じる処理と考えるとわかりやすいです。

```swift
extension UIApplication {//UIApplicationクラス拡張してキーボードを下げる。
    func endEditing() {
        sendAction(
            #selector(UIResponder.resignFirstResponder),
            to:nil,from:nil,for:nil
        )
    }
}
```

#2.メインビジュアルのコード
```swift
struct ContentView: View {
    @State var theText: String = ""
    var body: some View {
        NavigationView {
            TextEditor(text: $theText)
                .lineSpacing(10)
                .border(Color.gray)
                .padding([.leading,.bottom,.trailing])
                .navigationTitle("メモ")
                .toolbar{
                    //保存ボタン
                    ToolbarItem(placement: .navigationBarTrailing) {
                        Button {
                            UIApplication.shared.endEditing()//キーボードを下げる
                            saveText(theText, "sample.txt")//ユーザー定義関数のsaveText()テキストデータを保存する
                        } label: {
                            Text("保存")
                        }
                    }
                }
        }
    }

```

##2-1.ユーザー入力の値を保存する変数
@Stateをつけて変更可能な変数にする。@Stateをつけて宣言された変数は値の更新が見張られるようになり、値が変更したならば値を反映させるためにViewも更新される。
ユーザーの目線からも動向を追跡できる変数宣言といったところだろうか??
今回は言及しません。


```swift
@State var theText: String = ""
```


##2-2NavigtionViewでデザイン調整
TextEditorの引数に$theTextを渡すことでユーザーの入力した文字が表示されます。
細かいレイアウトを引数の後に記述しています。
.toolbarで保存ボタンの詳細なデザインや挙動を記載しています。

```swift
NavigationView {
            TextEditor(text: $theText)
                .lineSpacing(10)
                .border(Color.gray)
                .padding([.leading,.bottom,.trailing])
                .navigationTitle("メモ")
                .toolbar{
                    //保存ボタン
                    ToolbarItem(placement: .navigationBarTrailing) {
                        Button {
                            UIApplication.shared.endEditing()//キーボードを下げる
                            saveText(theText, "sample.txt")//ユーザー定義関数のsaveText()テキストデータを保存する
                        } label: {
                            Text("保存")
                        }
                    }
                }

```



##2-3保存ボタンの設定とテキスト保存のコード

保存ボタン設定しています。
ボタンを配置するツールバーはTexteddditorにモディファイアのtoolbarを追加して作ります。ツールバーにボタンを追加するには、toolbarのコンテンツとしてToolbarItem()の引数placementを指定するとツールバーの右端に寄った位置に表示されます。
先ほど記述したUIApllicationを呼び出し、endEditingを実行します。
saveText(theText,"sample.txt")ユーザー定義関数を呼び出し、メモを保存します。

```swift     
                .toolbar{
                    //保存ボタン
                    ToolbarItem(placement: .navigationBarTrailing) {
                        Button {
                            UIApplication.shared.endEditing()//キーボードを下げる
                            saveText(theText, "sample.txt")//ユーザー定義関数のsaveText()テキストデータを保存する
                        } label: {
                            Text("保存")
                        }
                    }
                }

```



#3.saveText()定義:テキストファイルへの保存を例外処理の中で行う。
.toolbarモディファイア内に記述したユーザー定義関数です。呼び出し側ではsaveText(ユーザーの入力値が入った変数,"ファイル名")でテキストデータを保存しています。
saveTextの第一引数でユーザーの入力値を受け取り、第二引数fileNameでファイル名を受け取ります。この例ではsample.txtという名前で受け取りますが、これだけでは保存する場所が決まりません。そこでユーザー定義関数docURL(filename)を実行して保存するURlを取得して、そのURLのパス(url.path)に書き込みます。なんらかの理由でnilになった時のためにguard let-elseを使います。

##3-1.aveTextユーザー定義関数

```swift
 func saveText(_ textData:String, _ fileName:String){
        //docURLはユーザー定義変数
        guard let url = docURL(fileName) else {
            return
        }
        //ファイルパスへの保存
        do {
            let path = url.path
            try textData.write(toFile: path, atomically: true, encoding:  .utf8)
        } catch let error as NSError {
            print(error)
        }
    }


```

##3-2.docURLユーザー定義関数

第一引数で(filename)を受け取ります。返り値はURL?で設定します。ファイルはiphoneデバイス内のどこでも保存できるというものではなく、目的に応じて決まっています。ユーザーが作って読み書きするファイルはDocumentsフォルダ内に保存します。DocumentsフォルダまでのURLはFileManager.defaultでfileManagerオブジェクトを作りfileManager.url()で取得する事ができます。DocumentsフォルダまでのURLを取得したならば、appendingPathComponent(fileName)で保存ファイル名を追加したURLを作り、そのURLをdocURL(fileName)の値として返します。
尚、ここでもエラーを返す場合があるのでdo-try-catchの例外処理の中で実行し、エラーになったならURLではなくnilを返します。nilを返す可能性があることから、戻り値の型はURL?のようにOputional型にします。


```swift
func docURL(_ fileName:String) -> URL? {
        let fileManager = FileManager.default
        do {
            //Documentsフォルダ
            let docsUrl = try fileManager.url(
                //Documentフォルダを指定する。
                for: .documentDirectory,
                in: .userDomainMask,
                appropriateFor: nil,
                create: false)
            let url = docsUrl.appendingPathComponent(fileName)
            return url
        }catch {
            return nil
        }
    }

```

#4.ツールバーに読み込みボタンを追加する。
読み込みボタンも保存ボタンと同じようにToolbarItemとしてツールバーに追加します。placementで指定する配置する位置は保存ボタンと同じく.navigationBarTralingですが、後から指定したバーアイテムの方が右端に来るので、コード順に従って「読み込み」「保存」の順番で並びます。
読み込みボタンではloadText("sample.txt")を実行してsample.txtからテキストデータを読み込みます。読み込みに失敗した場合はnilが返されるので、if let-elseのオプショナルバインディングを使って読み込んだデータを変数dataに代入し、nilでなければdataをtheTextに代入してテキストエディタに表示します。
loadTextユーザー定義関数については後述します。


##4-1読み込みボタン設置
読み込みボタンも保存ボタン同様,ToolbarItemとして.toolbar内に記述します。

```swift
.toolbar{
                    //読み込みボタン
                    ToolbarItem(placement: .navigationBarTrailing) {
                        Button {
                            if let data  = loadText("sample.txt") {
                                theText = data
                            }
                            } label: {
                                Text("読み込み")
                            }
                    }
                    //保存ボタン
                    ToolbarItem(placement: .navigationBarTrailing) {
                    ~略~

```

##4-2loadText()
読み込みボタンではユーザー定義関数のloadText("sample.txt")でsample.txtからテキストデータを読み込んでいます。saveText()同様、docURL(fileName)で読み込むファイルのURLを作り、String(contentsOf: url,encoding:.utf8)でurlからテキストデータを読み込んでいます。saaveText同様、do-try-catchの例外処理の中で実行します。
もしエラーになったならば関数の値としてnilを戻すので,戻り値の型はString?のようにoptional型にします。

```swift
 func loadText(_ fileName:String) -> String? {
        //URLを得られたらアンラップしてurlに代入
        guard let url = docURL(fileName) else {
            return nil
        }
        //urlからの読み込み
        do {
            let textData = try String(contentsOf: url, encoding: .utf8)
            return textData
        } catch {
            return nil
        }
    }


```

メモ帳保存機能、読み込み機能、書き込み機能に必要なものはURLを作るdocURL(),保存のsaveText(),読み込みのloadText(),キーボードを下げるextension UIApplication,テキストエディタ,保存ボタン,読み込みボタン,を作るstract ContentView,そして最後にプレビュー画面を作るstructContentView_previewsがあります。
以上。