LoginSignup
2
0

More than 1 year has passed since last update.

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

Posted at

きっかけ

音声読み上げでタブによる履歴Viewへ遷移した時に履歴が表示されない事に悩まされたため

仕様

事前準備

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

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

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

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)
}

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

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の改造

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()
            }
        }
    }
}

キーボードが邪魔

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

import UIKit

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

HistoryViewを改造

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で変数を設定し
呼び出し部分にて受け渡す

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

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

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

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

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

履歴を保存しよう

// ユーザーデフォルトに保存
func save(_ array: [String]) {
    UserDefaults.standard.set(array, forKey: "履歴")
}
// ユーザーデフォルトから読み込む
func load() -> [String] {
    UserDefaults.standard.stringArray(forKey: "履歴") ?? []
}
struct InputView: View {
// 中略
                Button("読み上げ") {
                    if text != "" {
                        textToSpeech(text)
                        // 一旦読み込んで上書き
                        history = load()
                        // 履歴の先頭へ最後の入力をインサート
                        history.insert(text, at: 0)
                        // 保存
                        save(history)
                    }
                }
// 略

これで勝つる

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

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()
        }
    }
}

完成!

最後に

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

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0