🐥

[SwiftUI]NavigationViewとTabViewの同時使用について

2021/07/06に公開

SwiftUINavigationViewTabViewを同時に使いたかったので、挙動を調べてみました。
結論としてはNavigationViewの中にTabViewを置かない方が無難、でした。

環境

Xcode : 12.5.1
iOS14.5

試した方法

  • ❌ NavigaitonView -> TabView -> NavigaitonViewとネストした方法
  • 🔺 NavigationView -> TabView
  • ○ TabView -> NavigationView
  • ○ NavigationViewを使わない方法

NavigationView -> TabView -> NavigationView

NavigationViewの中にTabViewがあり、さらにその中にNavigationViewがある場合です。

struct ContentView: View {
    
    @State var selection = 0
    
    var body: some View {
        NavigationView {

            TabView(selection: $selection) {
                FirstView()
                    .tabItem { Label("First", systemImage: "cloud.fill") }
                    .tag(0)
                SecondView()
                    .tabItem { Label("Second", systemImage: "moon.fill") }
                    .tag(1)
                
            }
            
        }
    }
}

struct FirstView: View {    
    
    var body: some View {
        
        NavigationView {
            VStack {
                Text("Hello, First!")
                
                NavigationLink.init(
                    destination: DetailView(),
                    label: { Text("Navigation") })
                
            }.navigationBarTitle(Text("First"), displayMode: .inline)
        }

    }
}

結果

NavigationViewの高さが変になってしまいました。navigationBarHiddenなどを指定しても変わりません。

NavigationView -> TabView

struct ContentView: View {
    
    @State var selection = 0
    
    var body: some View {
        NavigationView {

            TabView(selection: $selection) {
                FirstView()
                    .tabItem { Label("First", systemImage: "cloud.fill") }
                    .tag(0)
                SecondView()
                    .tabItem { Label("Second", systemImage: "moon.fill") }
                    .tag(1)
                
            }
            
        }
    }
}

struct FirstView: View {
        
    var body: some View {
        
        VStack {
            Text("Hello, First!")
            
            NavigationLink.init(
                destination: DetailView(),
                label: { Text("Navigation") })
            
        }

    }
}

struct DetailView: View {
        
    var body: some View {
        VStack {
            Text("Hello, Detail!")
        }.navigationBarTitle(Text("Detail"), displayMode: .inline)
    }
}

結果

一見、良さそうですが何やらエラーも表示されました。

Trying to pop to a missing destination at /Library/Caches/com.apple.xbs/Sources/Monoceros/Monoceros-103/Shared/NavigationBridge_PhoneTV.swift:337

ここら辺に原因があります。
https://stackoverflow.com/questions/64124502/swiftui-navigationview-trying-to-pop-to-missing-destination-monoceros

TabView -> NavigationView

TabViewの中にNavigationViewがある場合です。

struct FirstView: View {
        
    @Binding var showDetail: Bool
    @Binding var name: String
    
    var body: some View {
        
        NavigationView {
            VStack {
                Text("Hello, First!")
                
                Button("next") {
                    self.name = "First"
                    self.showDetail = true
                }
                
                NavigationLink.init("Navigation", destination: DetailView.init(name: .constant("Navigation")))
                                                
            }
        }

    }
}  

結果

動作には問題がなさそうです。タブを消したい場合は1つ前の方法が良いかもしれません。

NavigationViewを使わない方法

NavigationViewTabViewは同時に使わず、sheet等で画面を分ける方法を使用します。

struct ContentView: View {
    
    @State var selection = 0
    @State var showDetail = false
    @State var name: String = ""
    
    var body: some View {
      
        TabView(selection: $selection) {
            FirstView(showDetail: self.$showDetail, name: self.$name)
                .tabItem { Label("First", systemImage: "cloud.fill") }
                .tag(1)
            SecondView(showDetail: self.$showDetail, name: self.$name)
                .tabItem { Label("Second", systemImage: "moon.fill") }
                .tag(2)
        }.sheet(isPresented: self.$showDetail) {
            DetailView(name: self.$name)
        }
        
    }
}

struct FirstView: View {
        
    @Binding var showDetail: Bool
    @Binding var name: String
    
    var body: some View {
        
        VStack {
            Text("Hello, First!")
            
            Button("next") {
                self.name = "First"
                self.showDetail = true
            }
            
        }

    }
}

struct DetailView: View {
    
    @Binding var name: String
    
    var body: some View {
        VStack {
            Text("Hello, Detail!")
            Text("from \(name)")
        }
    }
}

結果

結論

NavigationViewTabViewを同時に使うと予期しない挙動になりそうなので、同時に使わない方が無難かもしれません。

Discussion