【SwiftUI】Tabでページ遷移時にリロードさせたい場合のメモ

#きっかけ

[音声読み上げ](https://qiita.com/mushroominger/items/5f4199d4eff8d2b4bc30)でタブによる履歴Viewへ遷移した時に履歴が表示されない事に悩まされたため

#仕様

###事前準備

タブを選択した時に表示される各View
違いが判るようにText("Hello, world!")をそれぞれ置換

```swift
struct InputView: View {
    var body: some View {
        Text("Hello, InputView!")
    }
}
```

```swift
struct HistoryView: View {
    var body: some View {
        Text("Hello, HistoryView!")
    }
}
```

させたい処理（とりあえず音声読み上げ）

```swift
import AVFoundation

func textToSpeech(_ text: String) {
    let utterance = AVSpeechUtterance(string: text)
    utterance.voice = AVSpeechSynthesisVoice(language: "ja-JP")
    utterance.rate = 0.5
    let synthesizer = AVSpeechSynthesizer()
    synthesizer.speak(utterance)
}
```

###タブによるページ遷移の実装

```swift
struct ContentView: View {
    @State var selection: Tab = .input

    enum Tab {
        case input
        case history
    }

    var body: some View {
        TabView(selection: $selection) {
            InputView()
                .tabItem {
                    Label("Input", systemImage: "pencil")
                }
                .tag(Tab.input)
            HistoryView()
                .tabItem {
                    Label("History", systemImage: "list.bullet")
                }
                .tag(Tab.history)
        }
    }
}
```

###InputViewの改造

```swift
struct InputView: View {
    @State var text: String = ""
    
    var body: some View {
        ZStack {
            Color.black
                .edgesIgnoringSafeArea(.all)
            Color.white
            Color.gray
                .opacity(0.3)
                .onTapGesture {
                    // キーボード・テキストフィールド・読み上げ・削除以外をタップすると発動
                    UIApplication.shared.closeKeyboard()
                }
            VStack {
                TextField("入力してください", text: $text)
                    .background(Color.white)
                    .font(.largeTitle)
                    .padding()
                Button("読み上げ") {
                    if text != "" {
                        textToSpeech(text)
                    }
                }
                .font(.largeTitle)
                .foregroundColor(.white)
                .background(Color.blue)
                .padding()
                Button("削　　除") {
                    text = ""
                }
                .font(.largeTitle)
                .foregroundColor(.white)
                .background(Color.red)
                .padding()
            }
        }
    }
}
```

###キーボードが邪魔

キーボード・テキストフィールド・読み上げ・削除以外をタップすると発動する関数を実装

```swift
import UIKit

extension UIApplication {
    func closeKeyboard() {
        sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}

```

###HistoryViewを改造

```swift
struct HistoryView: View {
    // とりあえずの値を設定
    @State var history: [String] = ["あいうえお","かきくけこ","さしすせそ"]
    
    var body: some View {
        NavigationView {
            List(history, id: \.self) {item in
                Button(item) {
                    textToSpeech(item)
                }
            }
            .navigationTitle("履　歴")
        }
    }
}
```

###履歴の内容を受け渡すための変数設定

親Viewに@Stateで変数を設定し
呼び出し部分にて受け渡す

```swift
struct ContentView: View {
    // 変数設定
    @State var history: [String] = []
// 中略
            // 呼び出し部分
            InputView(history: $history)
// 中略
            // 呼び出し部分
            HistoryView(history: $history)
//　略
```

子Viewにて@Binding設定で変数を追記

```swift
struct InputView: View {
    @Binding var history: [String]
// 中略
                Button("読み上げ") {
                    if text != "" {
                        textToSpeech(text)
                        // 受け渡し用の配列の先頭にインサート！
                        history.insert(text, at: 0)
                    }
                }
// 略
```

```swift
struct HistoryView: View {
    @Binding var history: [String]
// 略
```

#完成！とはいきませんでした

アプリを落とすと履歴が消えてしまう…

###履歴を保存しよう

```swift
// ユーザーデフォルトに保存
func save(_ array: [String]) {
    UserDefaults.standard.set(array, forKey: "履歴")
}
// ユーザーデフォルトから読み込む
func load() -> [String] {
    UserDefaults.standard.stringArray(forKey: "履歴") ?? []
}
```

```swift
struct InputView: View {
// 中略
                Button("読み上げ") {
                    if text != "" {
                        textToSpeech(text)
                        // 一旦読み込んで上書き
                        history = load()
                        // 履歴の先頭へ最後の入力をインサート
                        history.insert(text, at: 0)
                        // 保存
                        save(history)
                    }
                }
// 略
```

#これで勝つる

~~今時言いませんよね…~~
読み上げボタンを押せば読み込んで上書きされますが
普通に履歴タブを選択しただけでは読み込まれません
ではどこで読み込むのかが問題点です

```swift
struct HistoryView: View {
    @Binding var history: [String]
    
    var body: some View {
        NavigationView {
            List(history, id: \.self) {item in
                Button(item) {
                    textToSpeech(item)
                }
            }
            .navigationTitle("履　歴")
        }
        // .onAppearで活性化確認し履歴を上書き
        .onAppear{
            history = load()
        }
    }
}
```

完成！

#最後に

とりあえず履歴の読み込みができてポチッとするだけで読み上げてくれるレベルまで到達しました
え？
ダブってる履歴が邪魔だって？
今回はここまでです
疲れました…
今度考えます