【SwiftUI】TabView と NavigationView の組み合わせ

過去記事で TabView の使い方NavigationView & NavigationLink の使い方について紹介していますが、実際のアプリではこの2つを組み合わせることが多いと思います。

今回はその NavigationView の中に TabView を配置したパターンを紹介します。

TabView と NavigationView の組み合わせ

TabView は NavigationView の内側に置く

TabView の .tabItem にセットしている各Viewから画面遷移するには TabView を NavigationView で囲い、各Viewの中に NavigationLink を配置します。

NavigationView {
    TabView {
        TabView1().tabItem {
            Text("TabView1)
        }
    }
}
struct TabView1: View {
    var body: some View {
        VStack {
            Text("TabView1")
            NavigationLink(destination: SubView1()) {
                Text("Go to SubView1")
            }
        }
    }
}

このようにすると、TabView の上に遷移先の画面が覆い被さるように表示されます。

もし、TabView は表示したまま画面遷移したい場合は下記のように各Viewをそれぞれ NavigationView で囲う必要があります。

TabView {
    NavigationView {
        TabView1().tabItem {
            Text("TabView1")
        }
    }
    NavigationView {
        TabView2().tabItem {
            Text("TabView2")
        }
    }
}

ただしこの場合、遷移先の画面でタブを切り替えた時に不都合が生じないよう考慮する必要があるのでハンドリングが難しくなります(あまりこのような設計のアプリは見かけません)。

タブのタップ時にナビゲーションバーのタイトルを変更する

NavigationView が TabView の親であるため、ナビゲーションバーのタイトルは TabView 全体に紐づいているため、.tabItem の各View側で .navigationTitle を設定してもタイトルは変わりません

TabView の selection: プロパティと .onChange イベントを使ってこれを解決することができます。

enum Tabs: String {
    case tab1 = "Tab1"
    case tab2 = "Tab2"
    case tab3 = "Tab3"
}
    
@State private var navigationTitle: String = Tabs.tab1.rawValue
@State private var selectedTab: Tabs = .tab1
    
var body: some View {
    NavigationView {
        TabView(selection: $selectedTab) {
            TabView1().tabItem {
                Text(Tabs.tab1.rawValue)
            }
            .tag(Tabs.tab1)
            TabView2().tabItem {
                Text(Tabs.tab2.rawValue)
            }
            .tag(Tabs.tab2)
            TabView3().tabItem {
                Text(Tabs.tab3.rawValue)
            }
            .tag(Tabs.tab3)
        }
        .navigationTitle(navigationTitle)
        .onChange(of: selectedTab) { tab in
            navigationTitle = selectedTab.rawValue
        }
    }
}

@State プロパティとして変数 navigationTitle を宣言し、TabView の .navigationTitle にセットします(予め、enum でタブのタイトルを定義しています)。

また、こちらも @State プロパティで宣言した変数 selectedTab を TabView のイニシャライザーの selection: に紐付けます。

そして TabView の onChange イベントをハンドリングし、選択されたタブのタイトルを navigationTitle にセットします。

なお、TabView のそれぞれ View に .tag をセットしておかないと selectionTab との連携が取れないため、タブを切り替えても期待した動作をしてくれませんので忘れないように注意してください。

全体のコード

まとめますと、全体のコード(冒頭の動画)は以下のようになります。

ちなみに .sheet や .fullscreen でモーダル画面を表示する場合は TabView に配置せず、各Viewで配置しても画面全体を覆ってくれます(下記の TabView1 で PresentView を表示しています)。

import SwiftUI

struct ContentView: View {
    enum Tabs: String {
        case tab1 = "Tab1"
        case tab2 = "Tab2"
        case tab3 = "Tab3"
    }
    
    @State private var navigationTitle: String = Tabs.tab1.rawValue
    @State private var selectedTab: Tabs = .tab1
    
    var body: some View {
        NavigationView {
            TabView(selection: $selectedTab) {
                TabView1().tabItem {
                    Text(Tabs.tab1.rawValue)
                }
                .tag(Tabs.tab1)
                TabView2().tabItem {
                    Text(Tabs.tab2.rawValue)
                }
                .tag(Tabs.tab2)
                TabView3().tabItem {
                    Text(Tabs.tab3.rawValue)
                }
                .tag(Tabs.tab3)
            }
            .navigationTitle(navigationTitle)
            .onChange(of: selectedTab) { tab in
                navigationTitle = selectedTab.rawValue
            }
        }
    }
}

struct TabView1: View {
    @State var isActive = false
    var body: some View {
        VStack {
            Text("TabView1")
            NavigationLink(destination: SubView1()) {
                Text("Go to SubView1")
            }
            Button(action: {
                isActive = true
            }) {
                Text("Show Present View1")
            }
        }
        .navigationBarTitle(Text("TabView1"))
        .sheet(isPresented: $isActive) {
            PresentView1()
        }
    }
}

struct TabView2: View {
    var body: some View {
        VStack {
            Text("TabView2")
            NavigationLink(destination: SubView2()) {
                Text("Go to SubView2")
            }
        }
    }
}

struct TabView3: View {
    var body: some View {
        VStack {
            Text("TabView3")
            NavigationLink(destination: SubView3()) {
                Text("Go to SubView3")
            }
        }
    }
}

struct SubView1: View {
    var body: some View {
        Text("SubView1")
            .navigationBarTitle(Text("SubView1"))
    }
}

struct SubView2: View {
    var body: some View {
        Text("SubView2")
            .navigationBarTitle(Text("SubView2"))
    }
}

struct SubView3: View {
    var body: some View {
        Text("SubView3")
            .navigationBarTitle(Text("SubView3"))
    }
}

struct PresentView1: View {
    var body: some View {
        Text("PresentView1")
    }
}

以上

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です