Swift PlaygroundsでNavigationView関連を学ぶ

SwiftUI

概要:


iPadのアプリSwift Playgroundsの「グリッドの編集」のソースコードで、
NavigationViewとNavigationLinkを解説します。

また関連する
NavigationTitle、
navigationBarTitleDisplayMode(.inline)、
navigationViewStyle(.stack)
なども説明します。

「グリッドの編集」アプリの全体構成

まずは、「グリッドの編集」というアプリのソースコード構成を見ておきましょう。

このアプリは5個のSwiftファイルで構成されています。
以下に図にしてみました。

Swift Playgroundsのアプリ「グリッドの編集」のファイル構成図

SymbolGridファイルのメインViewから、
 ★「Edit」ボタンが押されると、そのメインViewは編集モードへ遷移
 ★「+」ボタンが押されると、追加を担当するSymbolPickerのViewへ
 ★どれかのシンボルが押されると、詳細表示を担当するSymbolDetailのViewへ
という、いたってシンプルな構成です。

 プログラムは全体像が図などで見えるとわかり易いと思います。

このViewの遷移はSwiftUIのエッセンスの1つでもあるので、詳しく説明していきます。

NavigationViewとNavigationLink

SwiftUIでのViewの遷移では、NavigationViewとNavigationLinkの組み合わせがよく使われます。このアプリでもメインのSymbolGridのViewと、SymbolDetailのViewとの遷移で使っていますので、見ていきましょう。

NavigationViewとNavigationLinkの役割

NavigationViewとNavigationLinkを使うと、基本的には以下のアニメのように、
 ★View間の移動を横スライドで滑らかに表現する
 ★上部にタイトルや戻るボタンを構成する
を自動的にやってくれます。

iPhoneアプリやiPadアプリでよく見る動きですよね!

ではソースコードを見ていきましょう。まずSymbolGridAppファイルです。

import SwiftUI

@main
struct SymbolGridApp: App {
    var body: some Scene {
        WindowGroup {
            NavigationView {   // ←ここ
                SymbolGrid()
            }
            .navigationViewStyle(.stack)
        }
    }
}

NavigationViewが、メインViewとなるSymbolGrid()を{ }で括っていますね。

括られたSymbolGridのViewは、その子Viewまで「NavigationViewの配下になる」という意味になります。

NavigationViewとSymbolGridのViewの関係をクリアにしておきましょう。
.borderモディファイアで境界に線を描く用に書き加えてみました。

import SwiftUI

@main
struct SymbolGridApp: App {
    var body: some Scene {
        WindowGroup {
            NavigationView {
                SymbolGrid()
                    .border(.green, width: 4) // ←追加
            }
            .border(.red, width: 2) // ←追加
            .navigationViewStyle(.stack)
        }
    }
}

NavigationViewの境界を赤線で、SymbolGridのViewの境界を緑線で描くように追加しています。

赤線で囲まれたNavigationViewの方が上部で広いことがわかりますね。この部分はNavigationViewが主で管理している領域になります。

SymbolGridのViewは緑で囲われた領域内で表示を担当することになります。

NavigationLinkの動作

では、NavigationLinkが記述されているSymbolGridファイルの方に移りましょう。

説明に不要な箇所は省略し、全体がわかるようにしています。

struct SymbolGrid: View {
    // 省略

    var body: some View {
        VStack {
        // 省略
            ScrollView {
                LazyVGrid(columns: gridColumns) {
                    ForEach(symbols) { symbol in
                        NavigationLink(destination: SymbolDetail(symbol: symbol)) {
                            ZStack(alignment: .topTrailing) {
                                Image(systemName: symbol.name)
                                // 省略
                            }
                            .padding()
                        } // NavigationLink End
                        .buttonStyle(.plain)
                    }
                }
            }
        }
        .navigationTitle("My Symbols")
        .navigationBarTitleDisplayMode(.inline)
        // 省略
    }
    
    // 省略
}

LazyVGridの中にForEachを使って、NavigationLinkを複数個(ここでは初期12個)を作っているイメージです。

LazyVGridとForEachについては別な解説で詳しく説明していますので、よければご参照ください。

NavigationLinkの意味は以下のようになります。

NavigationLink(destination:  labelが選択された時に遷移するView  ) {
    // 選択labelとなるViewを記述
}

実際の表記は以下になっていますので、シンボルImageが選択されたら、SymbolDetailのViewへ遷移するという記述になります。

NavigationLink(destination: SymbolDetail(symbol: symbol)) {
    ZStack(alignment: .topTrailing) {
         Image(systemName: symbol.name)
         // 省略
    }
    .padding()
}

SymbolDetailのソースコードにはNavigationに関する記述は一切ありませんので、NavigationViewの配下でこのNavigationLinkの記述だけですれば、SymbolDetailから「戻る」という仕組みが構築されることになります。

navigationTitleの位置

navigationTitleは、その名の通り上部のNavigationエリアに「My Symbols」というタイトルを表示するものですが、、

なんでVStackの終わりカッコ”}”につけてるの?

と気になって、色々実験して見ました(笑)

struct SymbolGrid: View {
    // 省略

    var body: some View {
        VStack {
        // 省略
            ScrollView {
                LazyVGrid(columns: gridColumns) {
                    ForEach(symbols) { symbol in
                        NavigationLink(destination: SymbolDetail(symbol: symbol)  ) {
                            ZStack(alignment: .topTrailing) {
                                Image(systemName: symbol.name) /*OK*/
                                // 省略
                            } /*OK*/
                            .padding()
                        } /*OK*/
                        .buttonStyle(.plain)
                    } /*OK*/
                } /*OK*/
            } /*OK*/
        }
        .navigationTitle("My Symbols")
        .navigationBarTitleDisplayMode(.inline)
        // 省略
    }
    
    // 省略
}
import SwiftUI

@main
struct SymbolGridApp: App {
    var body: some Scene {
        WindowGroup {
            NavigationView {
                SymbolGrid() /*OK*/
            } /*NG*/
            .navigationViewStyle(.stack)
        }
    }
}

/*OK*/箇所に.navigationTytleを記述した場合、どれも動作してました!(笑)

どうやらその画面内のView要素ならどれでも良いようです。

NavigationViewの{ }の後ろでは使えませんでした。どのViewが対象かわからないので、当然ですね。

.navigationBarTitleDisplayMode(.inline)

この行をコメントアウトすると、以下のようにタイトルが別の行に大きく表示されるようになります。
上部の「Edit」ボタンとかある行に、タイトルをinlineで挿入するという意味になります。

好みの問題ですが、貴重なエリアをもったいない使い方になってしまうので、このモディファイヤーを記述してinlineで使う方が多いと思います。

.navigationViewStyle(.stack)

iPad等の幅広いデバイスでは画面が横分割されて左側のサイドバーと言う領域にメインViewがデフォルトで表示してしまいます。以下にこの行がある場合とない場合の違いをアニメーションにしておきます。

まず、.navigationViewStyle(.stack)表記がある場合です。

.navigationViewStyle(.stack)表記がある場合

続いて、.navigationViewStyle(.stack)表記が無い場合です。

.navigationViewStyle(.stack)表記がない場合

NavigationView関連の注意事項

ここまでNavigationView関連の一連の記述を説明してきました。
SwiftUIのNavigationがどんなものか少し理解が深まったと思います。

ただ、残念ながら2022年秋の改訂でNavigation関連は大幅に変更され、NavigationViewや.navigationViewStyle等は「非推奨(Deprecated)」になってしまいました。

NavigationStackなど新しい概念を使うように推奨されています。
DevelopersのNavigationLinkのページ

今回はあくまでPlaygroundsのソースコードをそのまま解説させて頂きました。


今回はiPadのアプリSwift Playgroundsの中にある「グリッドの編集」のソースコードを使ってNavigationViewとNavigationLink関連を解説してみました。

ソースコードはシンプルで、色んな箇所を変更して試すにはうってつけではないでしょうか。

最後まで読んで頂いて、ありがとうございます。

次回は「グリッドの編集」の2回目として、Binding、dismiss、sheetあたりを解説してみようと思います。

ご意見、ご指摘等ありましたら、コメントを頂けると大変嬉しいです。

コメント

タイトルとURLをコピーしました