見出し画像

[SwiftUI]メモアプリに新規メモ機能を追加する Part.2

■初めに


こんにちわ、中川(Twitter)です。

さて、メモアプリの新規メモ機能追加 Part.2です。
Part.1の記事はこちらから⬇︎

この記事はサンプルアプリのカスタマイズを目的としています。
元となっているアプリ実装の記事はこちらから⬇︎


今回の目標:

◯メモリストのデータをアプリ全体でデータを共有   ⭕️
◯新規メモ生成用のViewを作成し遷移先に指定        ⬅︎
 ✅
◯新規メモの追加ロジック作成

使用する主な機能:

1.画面デザインの構築
・TextField
・TextEditor
2. View間の 画面遷移を実装
・NavigationView
・NavigationLink
・.
navigationBarItems
・.
navigationTitle
3. 画面遷移と同時に入力キーボードを出す
presentationMode
@FocusState
・DispatchQueue

新規View完成図:

View名は「NewMemoView」としました。
iPhone標準搭載のメモアプリを参考に、
見た目はできるだけシンプルなデザインとします。

完成後コード⬇︎

import SwiftUI

struct NewMemoView: View {

    // フォーカスが当たるTextFieldを判断するためのenum
    enum Field { case title }

    @FocusState private var foucsedField: Field?

    @State var newMemoTitle = ""
    @State var newMemoText = ""

    // Home画面に戻るためのプロパティ
    @Environment(\.presentationMode) private var presentationMode
    @EnvironmentObject var vm: MemoData

    var body: some View {

        VStack {
            // メモのタイトルを記入するテキストボックス
            TextField("タイトル", text: $newMemoTitle)
                .padding(20)
                .frame(width: 400, height: 50)
                .font(.title)
                .focused($foucsedField, equals: .title)

            // メモ詳細を記入するテキストボックス
            TextEditor(text: $newMemoText)
                .padding(.horizontal)

        } // VStack
        .navigationBarTitle(Text(""),
                            displayMode: .inline)
        .navigationBarItems(trailing: Button("完了") {

            self.presentationMode.wrappedValue.dismiss()

        }.padding())

        // .onApperはView表示時に発生する処理のこと
        // NewMemoViewへの遷移から、0.5秒ずらしてenumの値を更新し、キーボード表示の処理を行う
        .onAppear() {

            DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) {
                foucsedField = .title
            }
        }

    } // body
} // View


[ 🍎 実装開始 🍎 ]


■新規メモ生成用のViewを作成 

Part.2では、新規メモ作成用のViewを
新しく作っていきます。
新しいメモタイトルと内容テキストを書く場所ですね。
NavigationViewやNavigationLinkを用いた
画面遷移も入ってきます。

では、実装していきましょう。
よろしくお願いします✊

1.画面デザインの構築


新しくSwiftUIViewファイル「NewMemoView」を作成します。

まずは画面全体の構成を作っていきましょう。⬇︎

NewMemoView全体の構成


struct NewMemoView: View {

    // フォーカスが当たるTextFieldを判断するためのenum
    enum Field: Hashable { case title }

    @State var newMemoTitle = ""   // ①
    @State var newMemoText = ""    // ②

    var body: some View {

        VStack {
            // メモのタイトルを記入するテキストボックス
            TextField("タイトル", text: $newMemoTitle)   // ✅ ①
                .padding(20)
                .frame(width: 400, height: 50)
                .font(.title)

            // メモ詳細を記入するテキストボックス
            TextEditor(text: $newMemoText)   // ✅ ②
                .padding(.horizontal)

        } // VStack
    } // body
} // View

①TextField

新規メモのタイトルを記入するテキストフィールド。
引数に状態変数(newMemoTitle)を指定して
データバインディングをする必要があります。

②TextEditor

新規メモの本文を記入するテキストエディター。
引数に状態変数(newMemoText)を指定して
データバインディングをする必要があります。


2. View間の 画面遷移を実装


新規メモのViewを用意できましたね。次は、
メイン画面のMemoListViewからNewMemoView
画面遷移ができるようにしていきます。
まず、MemoListViewに記述を足していきましょう。

struct MemoListView: View {

    // MemoModelのデータを呼び出し
    @EnvironmentObject var vm: MemoData
    // 新規メモViewへの遷移フラグを管理
    @State private var isActive = false   // ✅

Bool型の状態変数を用意します。後の設定で
このプロパティを遷移のフラグとして使用します。
このプロパティがtrueに更新されることでNewMemoViewへと
遷移するという流れですね。

続いてNavigationLinkを設定していきます。
今回NavigationViewの画面遷移を
Buttonからプッシュ遷移で実装します。
NavigationViewの配下に記述していきましょう。

MemoListView


①NavigationLinkを設定

// 新規メモViewへの画面遷移に使用
                // 下部のボタンとisActiveプロパティで紐づけている   ✅ ①
                NavigationLink(destination: NewMemoView(), isActive: $isActive) {
                    EmptyView()
                }

引数destination:
遷移先のViewを指定します。NewMemoView()とします。

引数isActive:
遷移を知らせるフラグBinding<Bool>を指定します。
先ほど作ったBool型の状態変数isActiveとします。

EmptyView()とすることで、NavigationLinkにより
自動配置されるアイテムを隠すことができます。
次に、プッシュ遷移と紐づけるButtonを作成します。

②ボタンによるプッシュ遷移の実装

// 新規メモViewへの画面遷移に使用
                // 下部のボタンとisActiveプロパティで紐づけている   
                NavigationLink(destination: NewMemoView(), isActive: $isActive) {
                    EmptyView()
                }

// 新規メモ生成ボタン
                Button(action: {

                    isActive.toggle()    // ✅

                }) {
                    Image(systemName: "square.and.pencil")
                        .resizable()
                        .scaledToFit()
                        .frame(width: 25, height: 25)
                        .padding(.top)
                }
            }

NavigationLinkで指定したisActiveを
ボタンアクションによって、.toggle()します。→trueに更新
これで、ボタンを押すことにより
NavigationLinkにフラッグが渡され、画面遷移します⬇︎

ボタンタップによりNewMemoViewにプッシュ遷移

③遷移元に戻るためのコードを実装

struct NewMemoView: View {

    @State var newMemoTitle = ""
    @State var newMemoText = ""

    // Home画面に戻るためのプロパティ
    @Environment(\.presentationMode) private var presentationMode  // ✅ ①

    var body: some View {

        VStack {
            // メモのタイトルを記入するテキストボックス
            TextField("タイトル", text: $newMemoTitle)
                .padding(20)
                .frame(width: 400, height: 50)
                .font(.title)

            // メモ詳細を記入するテキストボックス
            TextEditor(text: $newMemoText)
                .padding(.horizontal)

        } // VStack
        .navigationBarTitle(Text(""),displayMode: .inline) 
        .navigationBarItems(trailing: Button("完了") {       

            self.presentationMode.wrappedValue.dismiss() // ✅ ②

        }.padding())

    } // body
} // View


@Environmentを付与してpresentationModeを取得します。
下記の記事がより詳しく書かれているのでどうぞ⬇︎

// Home画面に戻るためのプロパティ
    @Environment(\.presentationMode) private var presentationMode  // ✅ ①



presentationModeにアクセスして、.dissmiss()を指定します。
これにより、現在のViewが閉じるという処理が実行されます。
完了ボタンを押すことで、遷移元のMemoListViewに戻るという流れです。

.navigationBarItems(trailing: Button("完了") {       

            self.presentationMode.wrappedValue.dismiss() // ✅ ②

        }



3. 画面遷移と同時に入力キーボードを出す


iPhone標準搭載のメモアプリだと、
新規メモ画面へ遷移すると入力キーボードが
自動で出現するようになっています。
これもコードで実装されることで実現する挙動です。
今回試しに実装してみましょう✊

NewMemoViewに以下の4つのコードを記述していきます。
少し多いですがそこまで複雑ではないのでやってみましょう✊

struct NewMemoView: View {

    // フォーカスのフラグに使用するenum  
    enum Field { case title }   // ✅ ①

    @FocusState private var foucsedField: Field?  // ✅ ②

    @State var newMemoTitle = ""
    @State var newMemoText = ""

    // Home画面に戻るためのプロパティ
    @Environment(\.presentationMode) private var presentationMode 

    var body: some View {

        VStack {
            // メモのタイトルを記入するテキストボックス
            TextField("タイトル", text: $newMemoTitle)
                .padding(20)
                .frame(width: 400, height: 50)
                .font(.title)
                .focused($foucsedField, equals: .title)  // ✅ ③

            // メモ詳細を記入するテキストボックス
            TextEditor(text: $newMemoText)
                .padding(.horizontal)

        } // VStack
        .navigationBarTitle(Text(""),displayMode: .inline) 
        .navigationBarItems(trailing: Button("完了") {       

            self.presentationMode.wrappedValue.dismiss() 

        }.padding())

        // .onApperはView表示時に発生する処理のこと
        // NewMemoViewへの遷移から、0.5秒ずらしてenumの値を更新し、キーボード表示の処理を行う
        .onAppear() {   // ✅ ④

            DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) { 
                foucsedField = .title
            }
        }

    } // body
} // View


フォーカスのフラグを管理するための列挙型enumを宣言します⬇︎

// フォーカスのフラグに使用するenum  
    enum Field { case title }   // ✅ ①

② 
@FocusedStateを付与したプロパティを宣言します。
型はenumで定義した名前を?で
オプショナルラップして宣言しましょう⬇︎

@FocusState private var foucsedField: Field?  // ✅ ②



.focusedをフォーカス制御したい対象に付与します。
②のfoucsedFieldをバインディングで指定し、
引数equals: にenumで作成したケースを指定します。
これにより、enumがtitle.になったときに
対象がフォーカスされるようになります。

.focused($foucsedField, equals: .title)  // ✅ ③



.onApperのクロージャ内に処理を記述することで、
対象のViewが表示されたタイミングで処理を実行させることができます。
つまり、NewMemoViewに画面遷移したた時、処理が実行されます。

クロージャ内の処理を見ていきます、これが大事です⬇︎


        .onAppear() {   // ✅ ④

            DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) { 
                foucsedField = .title
            }
        }

DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) { 処理 }

これは何かというと、クロージャ内の処理を0.6秒遅らせます。
これが.onAppear内にあることで、
画面表示から0.6秒後にクロージャ内の処理を実行する
という指令をすることができます。
0.6秒後に、FoucsedFieldの値を.titleに更新しています。

:なぜ処理を遅らせることが必要?

入力キーボードはテキストフィールドに
フォーカスがされた時に出現します。
今回はそれを自動で出現させようとしているわけですが、
DispachQueueによる遅延が無いと、画面遷移と
フォーカス判定のタイミングが噛み合わないようで、
フォーカスがうまく実行されません
なので、画面遷移が無事終わるまでフォーカス判定の実行を
少し待ってあげている
ということですね。
遅延時間設定は色々試してみましたが、
0.5秒まではうまく発動しない時がありました。
0.6秒だと現在安定しています。

これで、画面遷移と同時に入力キーボードが
出現してくれるようになりました。お疲れ様です✊

自動でタイトル入力欄にフォーカスがされ、キーボードが自動で出現


■まとめ


なかなか盛りだくさんとなってしまいましたね🙇‍♂️
次は最後のパート、リスト内に新規メモの内容が追加されるよう
実装していきます。
ここまで読んでいただきありがとうございました!

ではでは🙌



[一緒に読んでほしい記事]


◆メモサンプルアプリ作成 

◆本記事のPart1 

以上

この記事が気に入ったらサポートをしてみませんか?