見出し画像

ChatGPTのレクチャーは有料級!iPhoneアプリの画面遷移方法について教えてもらった(swiftUI, navigationDestination)

日々ChatGPTにSwiftUIをレクチャーをしてもらっています!
最近はお勉強用にクイズアプリを作っていますが、画面遷移周りを色々いじってたら、想定通りに動かず、NavigationStackや、navigationDestinationの仕組みがよくわからなかったので、ChatGPTに簡単な画面遷移パターンをベースに仕様を教えてもらおうと思います!

前回までのswiftUIでのアプリ開発

1.NavigationStackや、navigationDestinationを使用した画面遷移について、ChatGPTに教えてもらう

1-1.教えてもらう画面遷移パターンについて

1-2.ChatGPTの回答

import SwiftUI

struct ContentView: View {
    @State private var path = NavigationPath()

    var body: some View {
        NavigationStack(path: $path) {
            ViewZ(path: $path)
                .navigationDestination(for: String.self) { view in
                    switch view {
                    case "ViewA":
                        ViewA(path: $path)
                    case "ViewB":
                        ViewB(path: $path)
                    case "ViewC":
                        ViewC(path: $path)
                    default:
                        Text("Unknown View")
                    }
                }
        }
    }
}

struct ViewZ: View {
    @Binding var path: NavigationPath

    var body: some View {
        VStack {
            Text("This is View Z")
            Button(action: {
                path.append("ViewA")
            }) {
                Text("Go to View A")
            }
        }
    }
}

struct ViewA: View {
    @Binding var path: NavigationPath

    var body: some View {
        VStack {
            Text("This is View A")
            Button(action: {
                path.append("ViewB")
            }) {
                Text("Go to View B")
            }
        }
    }
}

struct ViewB: View {
    @Binding var path: NavigationPath

    var body: some View {
        VStack {
            Text("This is View B")
            Button(action: {
                path.append("ViewC")
            }) {
                Text("Go to View C")
            }
        }
    }
}

struct ViewC: View {
    @Binding var path: NavigationPath

    var body: some View {
        VStack {
            Text("This is View C")
            Button(action: {
                // Clear the path to return to ViewA
                path.removeLast(path.count)
                path.append("ViewA")
            }) {
                Text("Go to View A")
            }
        }
    }
}

@main
struct YourApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

詳細な説明
NavigationStackとNavigationPath:
NavigationStackと@State変数pathを使って、ナビゲーションの履歴を管理します。
@Bindingを使用して各ビューでナビゲーションパスにアクセスします。

Viewの定義:
各ビュー(ViewZ, ViewA, ViewB, ViewC)はそれぞれ次のビューに遷移するボタンを持ちます。
ViewCのボタンでは、pathをクリアし、ViewAを追加することで、ViewAに戻るロジックを実装します。

これにより、viewZからviewA、viewAからviewB、viewBからviewC、そしてviewCからviewAに戻ることができるシンプルなナビゲーションフローを実現できます。

2.ContentViewのロジック詳細

それでは詳細のロジックを見ていきます!
ContentViewのロジックが、ナビゲーション遷移を管理する基点となり、ここで遷移のルールを決めて、あとは各種viewでここのcase文箇所の遷移先を設定すれば遷移できるイメージとなります。

struct ContentView: View {
    @State private var path = NavigationPath()

    var body: some View {
        NavigationStack(path: $path) {
            ViewZ(path: $path)
                .navigationDestination(for: String.self) { view in
                    switch view {
                    case "ViewA":
                        ViewA(path: $path)
                    case "ViewB":
                        ViewB(path: $path)
                    case "ViewC":
                        ViewC(path: $path)
                    default:
                        Text("Unknown View")
                    }
                }
        }
    }
}

2-1.ContentViewで、NavigationStackを使用しビュー間の遷移を管理

ContentViewは、NavigationStackを使ってビュー間の遷移を管理します。NavigationPathを使用してナビゲーションの履歴を保持し、動的に目的のビューに遷移します。

2-2.@State private var path = NavigationPath()

これは、ナビゲーションスタックの履歴を保持するための状態変数です。NavigationPathは、ビューの遷移順序を記録します。

2-3.NavigationStack(path: $path)

NavigationStackは、ビューのナビゲーションを管理するためのコンテナです。pathパラメータに@State変数をバインドすることで、ナビゲーションパスを追跡します。

2-4.最初に表示したいビューをpathをバインドして設定する

ViewZ(path: $path):最初に表示されるビューです。pathをバインドして渡します。これにより、ViewZがナビゲーションパスを更新できるようになります。

2-5.navigationDestination(for: String.self) { view in ... }

これは、ナビゲーション先を動的に指定するためのクロージャです。ナビゲーションパスに追加された文字列に基づいて、次に表示するビューを決定します。

2-6.switch view { ... }:view

viewにはナビゲーションパスに追加された文字列が渡されます。この文字列に基づいて、表示するビューを切り替えます。
case "ViewA": ViewAビューを表示します。
case "ViewB": ViewBビューを表示します。
case "ViewC": ViewCビューを表示します。
default: 予期しない場合にはUnknown Viewを表示します。

3.各種Viewのロジックについて

ContentViewで遷移先ロジックを実装しましたが、使用する側はContentViewで定義したcase文の遷移先を、pathにappendするイメージとなります。

3-1.ContentViewの遷移先設定箇所

                    switch view {
                    case "ViewA":
                        ViewA(path: $path)
                    case "ViewB":
                        ViewB(path: $path)
                    case "ViewC":
                        ViewC(path: $path)

3-2.使用する側はpath.appendするだけ

struct ViewZ: View {
    @Binding var path: NavigationPath

    var body: some View {
        VStack {
            Text("This is View Z")
            Button(action: {
                path.append("ViewA")
            }) {
                Text("Go to View A")
            }
        }
    }
}

3-3.@Binding var path: NavigationPath

@Bindingプロパティラッパーを使用して、親ビューから渡されたNavigationPathを受け取っています。このNavigationPathは、ビューのナビゲーション履歴を保持するためのものです。@Bindingを使うことで、子ビュー(ここではViewZ)が親ビューの状態(NavigationPath)を直接操作できるようになります。

3-4.path.append("ViewA")

ここでは、pathに"ViewA"を追加しています。これにより、ナビゲーションスタックに"ViewA"が追加され、次に表示されるビューがViewAに対応するものになります。

4.viewCからviewAに遷移するロジック

viewCからviewAに遷移するロジックは上記パターンとは少々異なり、一度path(ナビゲーションの履歴管理)をリセットした後に、pathにviewAを設定するロジックとなります。

4-1.path.removeLast(path.count):

現在のナビゲーションパスをクリアします。path.countは現在のパスの長さを示しており、removeLastを使ってすべての要素を削除します。これにより、ナビゲーションパスがリセットされます。

4-2.path.append("ViewA"):

ナビゲーションパスに"ViewA"を追加します。これにより、次に表示されるビューがViewAに対応するものになります。

5.ナビゲーションパスをクリアする利点について

5-1.特定の状態に戻れるため、ユーザー体験を簡略化できる

ナビゲーション履歴のリセット:
アプリケーションのナビゲーション履歴が長くなると、特定のビューに戻るのが複雑になることがあります。例えば、ViewA -> ViewB -> ViewC -> ViewAのようなナビゲーションをしたい場合、単にビューをスタックに追加するだけではなく、途中のビューを無視して直接ViewAに戻りたいことがあります。

特定の状態に戻る:
アプリケーションの特定の状態に戻る必要がある場合、ナビゲーションパスをクリアして特定のビューに戻ることが重要です。これにより、ユーザーは不要なビューをスキップして目的のビューに直接到達できます。

ユーザー体験の簡略化:
ユーザーが簡単に特定のビューに戻れるようにすることで、アプリケーションの使い勝手が向上します。特に複雑なナビゲーションパスを持つアプリケーションでは、特定のビューに戻るための明確な手段を提供することが重要です。

5-2.メモリ等のパフォーマンス観点

SwiftUIのNavigationPathやNavigationStackは、アプリケーションのビューのナビゲーション履歴を保持します。一般的に、ビューの履歴が長くなると、以下のような問題が発生する可能性があります。

メモリ使用量の増加:
長いナビゲーションパスを保持することで、メモリ使用量が増加する可能性があります。特に大量のデータやリソースを持つビューを多く含む場合、メモリの負荷が高くなります。

パフォーマンスの低下:
ナビゲーション履歴が長くなると、パフォーマンスが低下することがあります。これは、戻る操作やナビゲーションの更新に時間がかかるためです。

不要なリソースの保持:
既に不要なビューやデータがメモリに残り続けることがあります。これは、メモリリークにつながり、アプリのパフォーマンスや安定性に悪影響を与える可能性があります。

実際の影響

具体的な影響は、アプリケーションの設計や使用されるデータの量、デバイスのメモリ容量などによって異なります。通常、以下のような場合に問題が顕著になります。
大規模なデータ: 各ビューが大量のデータを保持している場合。
複雑なUI: 各ビューが複雑なUIコンポーネントを持ち、多くのリソースを消費する場合。
長時間の使用: アプリが長時間使用され、ナビゲーション履歴がどんどん長くなる場合。

ナビゲーションパスをクリアする利点

ナビゲーションパスを適切にクリアすることで、これらの問題を軽減できます。
メモリの解放:
不要になったビューやデータをメモリから解放することで、メモリ使用量を削減できます。
パフォーマンスの向上:
ナビゲーション履歴をリセットすることで、ナビゲーション操作がスムーズになり、アプリ全体のパフォーマンスが向上します。
安定性の向上:
メモリリークを防ぎ、アプリの安定性を保つことができます。


結論

ナビゲーションパスをクリアしない場合でも、即座に問題が発生するわけではありませんが、特に長期間使用されるアプリケーションやメモリ消費が大きいアプリケーションでは、メモリ使用量の増加やパフォーマンスの低下が顕著になる可能性があります。適切にナビゲーションパスを管理し、不要な履歴をクリアすることで、これらの問題を予防することが重要です。

6.pathをリセットする場合としない場合の挙動について

pathをリセットする場合としない場合とで、「戻るボタン」について動作確認してみることにしました。

6-1.pathをリセットするロジックを使用した場合の戻るボタンの挙動

上記のロジックでの戻るボタンの挙動は以下。
ナビゲーション履歴がリセットされるので、以下グレー色の遷移履歴が削除されるイメージとなりました。

6-2.pathをリセットしないロジックを使用した場合の戻るボタンの挙動

ロジックの変更箇所は、以下リセット処理を削除したのみで、

path.removeLast(path.count)

戻るボタンの挙動を確認すると、通常通り遷移した順で戻れました。

おわりに

最後まで読んでいただき、ありがとうございます!

サーバーサイドのGET遷移のように簡単にできるのかなと思っていましたが、仕組みが全然違うので理解するのに少々苦労しました、、

NavigationPathや、navigationDestinationの処理イメージは、だいぶついてきましたので、あとは自分の作りたいアプリの画面遷移を設計し、どのタイミングでリセットするか?などを適宜検討していく必要がありそうです。

アドリブで作っていくと混乱しそうなので、事前に画面フローは整理しておく必要がありそうですね、、

遷移の仕方は今回のパターン以外にもありますので、その他の遷移パターンも今後整理していきたいと思います。

おまけ

最近、ChatGPTを使用し、色々なことを模索しています。
もしよければ、以下の記事も見て頂けると嬉しいです!



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