#きっかけ
音声読み上げでタブによる履歴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()
}
}
}
完成!
#最後に
とりあえず履歴の読み込みができてポチッとするだけで読み上げてくれるレベルまで到達しました
え?
ダブってる履歴が邪魔だって?
今回はここまでです
疲れました…
今度考えます