前節ではデータとViewを独立させ, 繰り返し処理(Foreach)を使ってデータの数の分だけリストを作ることを行いました。データは 構造体 でわかりやすく整理した上で配列に必要な分だけデータを追加することができるようになりました。今節では前節のデータ構造を用いながら,2-1節で触れた画面遷移を連携させていきます。

  • SFSwiftUI02-5.swiftpm を開く (Xcode) (Playground)

    • 中身が2-4を終えた段階と同じであることを確認する
  • MyApp.swiftを開き, NavigationView を追加, ContentViewを包む

    • NavigationViewに .navigationViewStyle( StackNavigationViewStyle() ) を追加する
    • StackNavigationViewStyle : iPhoneとiPadでナビゲーションの表示を同じにする

コード1

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import SwiftUI

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            NavigationView {
                ContentView()
                .preferredColorScheme( .light )
            }
            .navigationViewStyle( StackNavigationViewStyle() )
        }
    }
}

navigationStyleですが、iPadの場合画面の広さを活用してナビゲーションの表示を変えてくれます。 しかしiPhoneと同じように見せたいこともありますので、StackNavigationViewStyleを指定することでiPhoneと同じく動くようにしています。

次にContentViewのナビゲーション設定を行います。

  • ContentView.swiftを開き, Listに .navigationTitle を追加する

コード2

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import SwiftUI

struct ContentView: View {
    var photos:[PhotoData] = [
        PhotoData( type:"景色", imageName:"CC0_01", title:"景色1", message:"森と山です" ),
        PhotoData( type:"景色", imageName:"CC0_04", title:"景色2", message:"きれいな湖です" ),
        PhotoData( type:"景色", imageName:"CC0_06", title:"景色3", message:"フィレンツェです" ),
        PhotoData( type:"景色", imageName:"CC0_07", title:"景色4", message:"ひまわりです" ),
        PhotoData( type:"景色", imageName:"CC0_10", title:"景色5", message:"海に日が沈みます" ),
        PhotoData( type:"食べ物", imageName:"CC0_05", title: "ハンバーガーセット", message: "おいしいです" ),
        PhotoData( type:"食べ物", imageName:"CC0_08", title: "カフェラテ", message: "おちつきます" ),
        PhotoData( type:"食べ物", imageName:"CC0_09", title: "ケーキ", message: "あまいです" ),
        PhotoData( type:"食べ物", imageName:"CC0_12", title: "お酒", message: "酔います" )
    ]
    
    var body: some View {
        List {
            Section( "景色写真" ) {
                ForEach( photos.filter { $0.type == "景色" } ) { photo in
                    SceneryRow( data:photo )
                }
            }
            
            Section( "食べ物写真" ) {
                ForEach( photos.filter { $0.type == "食べ物" } ) { photo in
                    FoodAndDrinkRow( data:photo )
                }
            }
        }
        .listStyle( .grouped )
        .navigationTitle( "写真集" )
    }
}

結果1

swiftui_2_5_1_1.png

コード1のMyAppでコード2のContentViewをNavigationViewでつつんだため, 画面の上部に
大きなタイトルが表示されるようになります。ここまでで2-4で作ったのと同じリスト画面を再構築できました。

次に, 景色リストをタップした時に表示する詳細情報の画面を作ります。

  • 新しくFileを追加, SceneryDetail.swiftに名前を変更

swiftui_2_5_1_2.png

  • PhotoData構造体を受け取れるようdataを用意
    • dataの中身をViewの表示の.navigationTitle, Image, Textで使用する

コード3

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import SwiftUI

struct SceneryDetail : View {
    var data:PhotoData

    var body: some View {
        VStack {
            Image( data.imageName )
            .resizable()
            .scaledToFit()
            .padding( .bottom, 20 )
            
            Text( data.message )
            
            Spacer()
        }
        .padding()
        .navigationTitle( data.title )
    }
}

作成したSceneryDetailは景色リストを押した時の詳細画面です。dataに情報を渡して使います。SceneryDetailができたら, ContentViewに戻りSceneryDetailへの遷移を作ります。

  • ContentView.swiftを再び開き, 景色写真のSceneryRowをNavigationLinkで包む
    • SceneRowはタップすると画面遷移できる要素になる
    • NavigationLinkの使い方はButtonと同様. actionが処理, labelが見た目

コード4

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
struct ContentView: View {
    var photos:[PhotoData] = [
        PhotoData( type:"景色", imageName:"CC0_01", title:"景色1", message:"森と山です" ),
        PhotoData( type:"景色", imageName:"CC0_04", title:"景色2", message:"きれいな湖です" ),
        PhotoData( type:"景色", imageName:"CC0_06", title:"景色3", message:"フィレンツェです" ),
        PhotoData( type:"景色", imageName:"CC0_07", title:"景色4", message:"ひまわりです" ),
        PhotoData( type:"景色", imageName:"CC0_10", title:"景色5", message:"海に日が沈みます" ),
        PhotoData( type:"食べ物", imageName:"CC0_05", title: "ハンバーガーセット", message: "おいしいです" ),
        PhotoData( type:"食べ物", imageName:"CC0_08", title: "カフェラテ", message: "おちつきます" ),
        PhotoData( type:"食べ物", imageName:"CC0_09", title: "ケーキ", message: "あまいです" ),
        PhotoData( type:"食べ物", imageName:"CC0_12", title: "お酒", message: "酔います" )
    ]
    
    var body: some View {
        List {
            Section( "景色写真" ) {
                ForEach( photos.filter { $0.type == "景色" } ) { photo in
                    NavigationLink( destination:{
                        SceneryDetail( data:photo )
                    },
                    label: {
                        SceneryRow( data:photo )
                    })
                }
            }
            
            Section( "食べ物写真" ) {
                ForEach( photos.filter { $0.type == "食べ物" } ) { photo in
                    FoodAndDrinkRow( data:photo )
                }
            }
        }
        .listStyle( .grouped )
        .navigationTitle( "写真集" )
    }
}

景色リストを選択した時, 選んだ情報(photo)をdataに渡して, SceneryDetail画面を作成します。タップすると画面が切り替わります。

結果2

タップ前

swiftui_2_5_1_3.png

景色写真のリストをタップ後

swiftui_2_5_1_4.png

続けて, 食べ物写真の詳細画面も作成します。作り方は景色写真の詳細画面と同じです。

  • 新しくFileを追加, FoodAndDrinkDetail.swiftに名前を変更

swiftui_2_5_2_1.png

  • SceneryDetailと少しView要素を変えた画面にする
    • .font( .title2 ) : .title, .title2, .title3 を指定すると標準の章節項のサイズが使える

コード5

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import SwiftUI

struct FoodAndDrinkDetail : View {
    var data:PhotoData

    var body: some View {
        VStack( alignment:.leading ) {
            ZStack( alignment: .bottomTrailing ) {
                Image( data.imageName )
                .resizable()
                .scaledToFit()

                VStack {
                    Text( data.title )
                    .font( .system(size: 24.0) )
                    .foregroundColor( .white )
                    .padding()
                }
            }
            
            VStack( alignment:.leading ) {
                Text( "食べ物の紹介" )
                .font( .title2 )
                .padding( .bottom )
                
                Text( data.message )
                .font( .system(size: 14.0 ) )
            }
            .padding()
                        
            Spacer()
        }
        .navigationTitle( data.title )
    }
}

これも景色写真と同様にContentViewの食べ物写真にNavigationLinkを追加します。

  • ContentView.swiftを再び開き, 食べ物写真のFoodAndDrinkRowをNavigationLinkで包む
    • SceneryRowと同様, FoodAndDrinkRowが画面遷移できる要素になる

コード6

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
struct ContentView: View {
    var photos:[PhotoData] = [
        PhotoData( type:"景色", imageName:"CC0_01", title:"景色1", message:"森と山です" ),
        PhotoData( type:"景色", imageName:"CC0_04", title:"景色2", message:"きれいな湖です" ),
        PhotoData( type:"景色", imageName:"CC0_06", title:"景色3", message:"フィレンツェです" ),
        PhotoData( type:"景色", imageName:"CC0_07", title:"景色4", message:"ひまわりです" ),
        PhotoData( type:"景色", imageName:"CC0_10", title:"景色5", message:"海に日が沈みます" ),
        PhotoData( type:"食べ物", imageName:"CC0_05", title: "ハンバーガーセット", message: "おいしいです" ),
        PhotoData( type:"食べ物", imageName:"CC0_08", title: "カフェラテ", message: "おちつきます" ),
        PhotoData( type:"食べ物", imageName:"CC0_09", title: "ケーキ", message: "あまいです" ),
        PhotoData( type:"食べ物", imageName:"CC0_12", title: "お酒", message: "酔います" )
    ]
    
    var body: some View {
        List {
            Section( "景色写真" ) {
                ForEach( photos.filter { $0.type == "景色" } ) { photo in
                    NavigationLink( destination:{
                        SceneryDetail( data:photo )
                    },
                    label: {
                        SceneryRow( data:photo )
                    })
                }
            }
            
            Section( "食べ物写真" ) {
                ForEach( photos.filter { $0.type == "食べ物" } ) { photo in
                    NavigationLink( destination:{
                        FoodAndDrinkDetail( data:photo )
                    },
                    label: {
                        FoodAndDrinkRow( data:photo )
                    })
                }
            }
        }
        .listStyle( .grouped )
        .navigationTitle( "写真集" )
    }
}

これで食べ物写真リストもタップすると詳細画面に遷移するようになります。また景色写真とは異なる見た目の画面になっていることがわかると思います。

結果3

タップ前

swiftui_2_5_2_2.png

食べ物写真タップ後

swiftui_2_5_2_3.png

ここまでで, Navigationを使った画面遷移の作り方は一区切りとなります。

データを設計し,データの内容によって表示の仕方を変える, 画面を切り替える方法を紹介しました。特にデータと見た目を独立させる点がポイントです。

データの作り方や画面の使い方を工夫することで, 様々なAppの動作を作っていくことができます。ぜひご自身が美しいと感じるAppをデザインしてみてください。