Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pop to root view using Tab Bar in SwiftUI

Tags:

swift

swiftui

Is there any way to pop to root view by tapping the Tab Bar like most iOS apps, in SwiftUI?

Here's an example of the expected behavior.

enter image description here

I've tried to programmatically pop views using simultaneousGesture as follow:

import SwiftUI


struct TabbedView: View {
    @State var selection = 0
    @Environment(\.presentationMode) var presentationMode
    var body: some View {
                TabView(selection: $selection) {
            RootView()
                .tabItem {
                    Image(systemName: "house")
                    .simultaneousGesture(TapGesture().onEnded{
                        self.presentationMode.wrappedValue.dismiss()
                        print("View popped")
                    })
            }.tag(0)
                
            Text("")
                .tabItem {
                    Image(systemName: "line.horizontal.3")
            }.tag(1)
        }
    }
}

struct RootView: View {
    var body: some View{
        NavigationView{
            NavigationLink(destination:SecondView()){
        Text("Go to second view")
            }
        }
    }
}

struct SecondView: View {
    var body: some View{
        Text("Tapping the house icon should pop back to root view")
    }
}

But seems like those gestures were ignored.

Any suggestions or solutions are greatly appreciated

like image 611
Nguyễn Khắc Hào Avatar asked Feb 07 '20 08:02

Nguyễn Khắc Hào


People also ask

How do I use tab view in SwiftUI?

Press Cmd+N to create a new SwiftUI View, calling it “MainView”. Creating tabs is as easy as putting different views inside an instance of TabView , but in order to add an image and text to the tab bar item of each view we need to use the tabItem() modifier.

How do I hide TabBar in NavigationView when using SwiftUI?

If we want to hide the TabBar , we just write TabView into NavigationView , making the NavigationView the super-view and the TabView the child-view, which is just opposite to the above View Hierarchy .


2 Answers

I messed around with this for a while and this works great. I combined answers from all over and added some stuff of my own. I'm a beginner at Swift so feel free to make improvements.

Here's a demo.

enter image description here

This view has the NavigationView.

import SwiftUI

struct AuthenticatedView: View {
    
    @StateObject var tabState = TabState()
            
    var body: some View {
        TabView(selection: $tabState.selectedTab) {
            NavigationView {
                NavigationLink(destination: TestView(titleNum: 0), isActive: $tabState.showTabRoots[0]) {
                    Text("GOTO TestView #1")
                        .padding()
                        .foregroundColor(Color.white)
                        .frame(height:50)
                        .background(Color.purple)
                        .cornerRadius(8)
                }
                .navigationTitle("")
                .navigationBarTitleDisplayMode(.inline)
            }
            .navigationViewStyle(.stack)
            .onAppear(perform: {
                tabState.lastSelectedTab = TabState.Tab.first
            }).tabItem {
                Label("First", systemImage: "list.dash")
            }.tag(TabState.Tab.first)
            
            NavigationView {
                NavigationLink(destination: TestView(titleNum: 0), isActive: $tabState.showTabRoots[1]) {
                    Text("GOTO TestView #2")
                        .padding()
                        .foregroundColor(Color.white)
                        .frame(height:50)
                        .background(Color.purple)
                        .cornerRadius(8)
                }.navigationTitle("")
                    .navigationBarTitleDisplayMode(.inline).navigationBarTitle(Text(""), displayMode: .inline)
            }
            .navigationViewStyle(.stack)
            .onAppear(perform: {
                tabState.lastSelectedTab = TabState.Tab.second
            }).tabItem {
                Label("Second", systemImage: "square.and.pencil")
            }.tag(TabState.Tab.second)
        }
        .onReceive(tabState.$selectedTab) { selection in
            if selection == tabState.lastSelectedTab {
                tabState.showTabRoots[selection.rawValue] = false
            }
        }
    }
}

struct AuthenticatedView_Previews: PreviewProvider {
    static var previews: some View {
        AuthenticatedView()
    }
}

class TabState: ObservableObject {
    enum Tab: Int, CaseIterable {
        case first = 0
        case second = 1
    }
        
    @Published var selectedTab: Tab = .first
    @Published var lastSelectedTab: Tab = .first
    
    @Published var showTabRoots = Tab.allCases.map { _ in
        false
    }
}

This is my child view

import SwiftUI

struct TestView: View {
    
    let titleNum: Int
    let title: String
    
    init(titleNum: Int) {
        self.titleNum = titleNum
        self.title = "TestView #\(titleNum)"
    }
       
    var body: some View {
        VStack {
            Text(title)
            NavigationLink(destination: TestView(titleNum: titleNum + 1)) {
                Text("Goto View #\(titleNum + 1)")
                    .padding()
                    .foregroundColor(Color.white)
                    .frame(height:50)
                    .background(Color.purple)
                    .cornerRadius(8)
            }
            NavigationLink(destination: TestView(titleNum: titleNum + 100)) {
                Text("Goto View #\(titleNum + 100)")
                    .padding()
                    .foregroundColor(Color.white)
                    .frame(height:50)
                    .background(Color.purple)
                    .cornerRadius(8)
            }
            .navigationTitle(title)
            .navigationBarTitleDisplayMode(.inline)
        }
    }
}

struct TestView_Previews: PreviewProvider {
    static var previews: some View {
        TestView(titleNum: 0)
    }
}
like image 60
Bropane Avatar answered Sep 25 '22 19:09

Bropane


We can use tab bar selection binding to get the selected index. On this binding we can check if the tab is already selected then pop to root for navigation on selection.

struct ContentView: View {

@State var showingDetail = false
@State var selectedIndex:Int = 0

var selectionBinding: Binding<Int> { Binding(
    get: {
        self.selectedIndex
    },
    set: {
        if $0 == self.selectedIndex && $0 == 0 && showingDetail {
            print("Pop to root view for first tab!!")
            showingDetail = false
        }
        self.selectedIndex = $0
    }
)}

var body: some View {
    
    TabView(selection:selectionBinding) {
        NavigationView {
            VStack {
                Text("First View")
                NavigationLink(destination: DetailView(), isActive: $showingDetail) {
                    Text("Go to detail")
                }
            }
        }
        .tabItem { Text("First") }.tag(0)
        
        Text("Second View")
            .tabItem { Text("Second") }.tag(1)
    }
  }
}

struct DetailView: View {
 var body: some View {
    Text("Detail")
  }
}
like image 28
Usama Azam Avatar answered Sep 24 '22 19:09

Usama Azam