Pop to root view using Tab Bar in SwiftUI - swift

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.
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

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")
}
}

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.
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)
}
}

You can achieve this by having the TabView within a NavigationView like so:
struct ContentView: View {
#State var selection = 0
var body: some View {
NavigationView {
TabView(selection: $selection) {
FirstTabView()
.tabItem {
Label("Home", systemImage: "house")
}
.tag(0)
}
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
struct FirstTabView: View {
var body: some View {
NavigationLink("SecondView Link", destination: SecondView())
}
}
struct SecondView: View {
var body: some View {
Text("Second View")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
ContentView()
}
}
}

Related

Implementing Button in side menu

can someone Help me with fixing this. I want this code to work such as when I click the Home button on the side menu, it should take me to the Main View("This is the Main View"). I have tried using presenting sheets, however, presenting sheet doesn't look realistic. When the Home button is tapped, everything should disappear and only the Home Screen should come up with the side menu. I have tried writing up this code, however, I couldn't make the home button work. The codes are as below:
import SwiftUI
import Foundation
import Combine
struct Home: View {
#State var showMenu = false
#EnvironmentObject var userSettings: UserSettings
var body: some View {
let drag = DragGesture()
.onEnded {
if $0.translation.width < -100 {
withAnimation {
self.showMenu = false
}
}
}
return NavigationView {
GeometryReader {
geometry in
ZStack(alignment: .leading) {
MainView(showMenu: self.$showMenu)
.frame(width: geometry.size.width, height: geometry.size.height)
.offset(x: self.showMenu ? geometry.size.width/2 : 0)
.disabled(self.showMenu ? true : false)
if self.showMenu {
MenuView()
.frame(width: geometry.size.width/2)
.transition(.move(edge: .leading))
}
}
.gesture(drag)
}
.navigationBarTitle("Pay Data", displayMode: .inline)
.navigationBarItems(leading: (Button(action: {
withAnimation {
self.showMenu.toggle()
}
}){
Image(systemName: "line.horizontal.3")
.imageScale(.large)
}
))
}
}
}
struct MainView: View {
#Binding var showMenu: Bool
#EnvironmentObject var userSettings: UserSettings
var body: some View {
Text("This is Main View")
}
}
struct Home_Previews: PreviewProvider {
static var previews: some View {
Home()
.environmentObject(UserSettings())
}
}
//This is the Menu View. The Home Button is located in this view.
import SwiftUI
import Combine
import Foundation
struct MenuView: View {
#EnvironmentObject var userSettings: UserSettings
#State var showMenu = false
#State var Homevariable = false
var body: some View {
VStack(alignment: .leading) {
Button(action: {
UserDefaults.standard.set(false, forKey: "status")
}) {
(Text(Image(systemName: "rectangle.righthalf.inset.fill.arrow.right")) + (Text("Home")))
}
}
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color(red: 32/255, green: 32/255, blue: 32/255))
.edgesIgnoringSafeArea(.all)
}
}
struct MenuView_Previews: PreviewProvider {
static var previews: some View {
MenuView()
.environmentObject(UserSettings())
}
}
//This is the another view. I want the side Menu to appear on this as well, so when I press the Home button it takes me to the Main View("This is the Main View")
import SwiftUI
struct Calculation: View {
var body: some View {
Text("Hello, World!")
}
}
struct Calculation_Previews: PreviewProvider {
static var previews: some View {
Calculation()
}
}
Here you go. You are basically rebuilding a navigation logic, so in MainView you have to switch between the screens and put the side menu over it:
(PS: you can do without GeometryReader)
struct ContentView: View {
#State private var showMenu = false
#State private var selected: SelectedScreen = .home
var body: some View {
NavigationView {
ZStack {
// show selected screen
switch selected {
case .home:
MainView()
.disabled(self.showMenu ? true : false)
case .screen1:
OtherView(screen: 1)
case .screen2:
OtherView(screen: 2)
}
// put menu over it
if self.showMenu {
MenuView(showMenu: $showMenu, selected: $selected)
.transition(.move(edge: .leading))
}
}
.navigationBarTitle("Pay Data", displayMode: .inline)
// .navigationBarItems is deprecated, use .toolbar
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
withAnimation {
self.showMenu.toggle()
}
} label: {
Image(systemName: "line.horizontal.3")
.imageScale(.large)
}
}
}
}
}
}
enum SelectedScreen {
case home
case screen1
case screen2
}
struct MenuView: View {
#Binding var showMenu: Bool
#Binding var selected: SelectedScreen
var body: some View {
HStack {
VStack(alignment: .leading, spacing: 24) {
Button {
selected = .home
showMenu = false
} label: {
Label("Home", systemImage: "rectangle.righthalf.inset.fill.arrow.right")
}
Button {
selected = .screen1
showMenu = false
} label: {
Label("Screen 1", systemImage: "1.circle")
}
Button {
selected = .screen2
showMenu = false
} label: {
Label("Screen 2", systemImage: "2.circle")
}
}
.padding()
.frame(maxHeight: .infinity)
.background(Color(red: 32/255, green: 32/255, blue: 32/255))
Spacer()
}
}
}
struct MainView: View {
var body: some View {
Text("This is Main View")
.font(.largeTitle)
}
}
struct OtherView: View {
let screen: Int
var body: some View {
Text("Other View: Screen \(screen)")
.font(.largeTitle)
}
}

SwiftUI: How can I move texts or something else with VStack, HStack?

I'm new in SwiftUI. I would like on the top left in the corner a Button next to the Picker. But when I place the Button, the Picker moves to the right and the Button is to nearly on the edge. How can I place the Button flush over the Headline and the Picker perfectly in the middle from iPhone Nodge?
Before:
After:
import SwiftUI
import Combine
struct ContentView: View {
#State var Selection = UserDefaults.standard.integer(forKey: "Picker")
#State var Detail = false
var body: some View {
VStack {
HStack {
Button(action: {
self.Detail.toggle()
}) {
Text("click")
}.sheet(isPresented: $Detail) {
SettingView(showSheetView: self.$Detail, selection: $Selection)
}
Picker("", selection: $Selection) {
Text("Selection1").tag(0)
Text("Selection2").tag(1)
}
.pickerStyle(SegmentedPickerStyle()).padding(.horizontal, 89)
.onReceive(Just(Selection)) {
UserDefaults.standard.set($0, forKey: "Picker")
}
}
PageOne()
}
}
}
struct PageOne: View {
var body: some View {
NavigationView {
VStack {
Text("some Text")
}.navigationTitle("Headline")
}
}
}
struct SettingView: View {
#Binding var showSheetView: Bool
#Binding var selection: Int
var body: some View {
NavigationView {
Text("Test")
.navigationBarTitle(Text("Select something"))
.navigationBarItems(trailing: Button(action: {
self.showSheetView = false
}) {
Text("Ok")
.bold()
})
}
}
}
you can use a ZStack to show your Button and Picker without pushing them.
something like this:
var body: some View {
VStack {
ZStack {
HStack {
Button(action: {
self.Detail.toggle()
}) {
Text("click")
}.sheet(isPresented: $Detail) {
SettingView(showSheetView: self.$Detail, selection: $Selection)
}
Spacer()
}
HStack {
Spacer()
Picker("", selection: $Selection) {
Text("Selection1").tag(0)
Text("Selection2").tag(1)
}
.pickerStyle(SegmentedPickerStyle()).padding(.horizontal, 89)
.onReceive(Just(Selection)) {
UserDefaults.standard.set($0, forKey: "Picker")
}
Spacer()
}
}
PageOne()
}
}

How to change TintColor of NavigationBar only one screen

I want to change the TintColor of the NavigationBar.
In the following implementation, I want to change only "DetailView", but the color of the screen of "EditView" also changes.
How can I change only one screen?
struct TopView: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: DetailView()) {
Text("Detail")
}
}
.navigationBarTitle("Top")
}
}
}
struct DetailView: View {
init(title: String) {
UINavigationBar.appearance().tintColor = UIColor(named: "White")
}
var body: some View {
VStack {
NavigationLink(destination: EditView()) {
Text("Edit")
}
}
.navigationBarTitle("Detail", displayMode: .inline)
}
}
struct EditView: View {
#Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
var body: some View {
VStack {
Text("Title")
}
.navigationBarTitle("Edit", displayMode: .inline)
.navigationBarItems(
trailing:
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Text("Save")
}
)
}
}
If I'm understanding the question aright then this might be a solution. It works by figuring out where we are in the navigation stack and then changing the accent color depending on the position. This changes the color of the navigation bar buttons. If you are relying on the accent color for other things, you'll need to override that, and I've illustrated that on the EditView. (I've left some logging so you can see how it works.)
enum ViewShowing : String {
case top
case detail
case edit
}
class ViewShowingModel : ObservableObject {
#Published var showing = ViewShowing.top
}
struct ContentView: View {
#ObservedObject var viewModel = ViewShowingModel()
var body: some View {
return NavigationView {
VStack {
NavigationLink(destination: DetailView(title: "Detail", viewModel: viewModel)) {
Text("Go to: Detail")
}
Text("viewShowing: \(viewModel.showing.rawValue)")
}.onAppear(perform: {
print("top appear")
self.viewModel.showing = .top
})
.navigationBarTitle("Top").accentColorForView(viewModel.showing)
}.accentColorForView(viewModel.showing)
}
}
struct DetailView: View {
var title : String
#ObservedObject var viewModel : ViewShowingModel
var body: some View {
return VStack {
NavigationLink(destination: EditView(viewModel: viewModel)) {
Text("Go to Edit: \(title)")
}
Text("viewShowing: \(viewModel.showing.rawValue)")
.navigationBarTitle("\(title)", displayMode: .inline)
}
.onAppear(perform: {
print("detail appear")
self.viewModel.showing = .detail })
}
}
struct EditView: View {
#ObservedObject var viewModel : ViewShowingModel
#Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
var body: some View {
VStack {
Text("Editing View")
Text("viewShowing: \(viewModel.showing.rawValue)")
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Text("A Dismiss Button (no color override)")
}.padding()
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Text("Another Dismiss Button (color override)").foregroundColor(Color(UIColor.systemBlue))
}.padding()
}
.navigationBarTitle("Edit", displayMode: .inline)
.navigationBarItems(
trailing:
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Text("Save")
}
)
.onAppear(perform: {
print("edit appear")
self.viewModel.showing = .edit
})
}
}
extension View {
func accentColorForView(_ viewShowing : ViewShowing) -> some View {
switch viewShowing {
case .detail :
return accentColor(Color.red)
case .top :
return accentColor(Color.purple)
case .edit :
return accentColor(Color.green)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

SwiftUI navigationBarItems disappear in TabView

I have a view that has navigation bar items and I embed that view in a TabView. But when doing that, the bar items no longer appear. If I call the view outside of a TabView everything works as expected.
Below a small sample project to illustrate my issue, note that the TabView is not called on the initial ContentView but later down:
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView{
NavigationLink(destination: WarehouseOrderTabView()){
Text("Click me")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct WarehouseOrderTabView: View {
var body: some View {
TabView{
TabView1().navigationBarTitle("Dashboard")
.tabItem {
Image(systemName: "gauge")
Text("Dashboard")
}
TabView2().navigationBarTitle("Orders")
.tabItem {
Image(systemName: "list.dash")
Text("Orders")
}
}
}
}
struct TabView1: View {
var body: some View {
Text("TabView 1")
//I would expect to see those bar items when displaying tab 1
.navigationBarItems(trailing: (
HStack{
Button(action: {
}, label: {
Image(systemName: "arrow.clockwise")
.font(.title)
})
.padding(.init(top: 0, leading: 0, bottom: 0, trailing: 20))
Button(action: {
}, label: {
Image(systemName: "slider.horizontal.3")
.font(.title)
})
}
))
}
}
struct TabView2: View {
var body: some View {
Text("TabView 2")
}
}
What am I missing here?
A NavigationView can be embedded in a TabView and not vice-versa.
TabView contains different tabItem() (at most 5) that can contain your views.
This is how you can use it.
TabView1.swift
struct TabView1: View {
var body: some View {
NavigationView {
Text("TabView 1")
.navigationBarTitle("Dashboard")
.navigationBarItems(trailing:
HStack {
Button(action: {
// more code here
}) {
Image(systemName: "arrow.clockwise")
.font(.title)
}
Button(action: {
// more code here
}) {
Image(systemName: "slider.horizontal.3")
.font(.title)
}
}
)
}
}
}
TabView2.swift
struct TabView2: View {
var body: some View {
NavigationView {
NavigationLink(destination: YourNewView()) {
Text("TabView 1")
}
.navigationBarTitle("Orders")
}
}
}
ContentView.Swift
import SwiftUI
struct ContentView: View {
var body: some View {
TabView {
TabView1()
.tabItem {
Image(systemName: "gauge")
Text("Dashboard")
}
TabView2()
.tabItem {
Image(systemName: "list.dash")
Text("Orders")
}
}
}
}
Hope it helps :)

How to navigate to a new view from navigationBar button click in SwiftUI

Learning to SwiftUI. Trying to navigate to a new view from navigation bar buttton clicked.
The sample code below:
var body: some View {
NavigationView {
List(0...< 5) { item in
NavigationLink(destination: EventDetails()){
EventView()
}
}
.navigationBarTitle("Events")
.navigationBarItems(trailing:
NavigationLink(destination: CreateEvent()){
Text("Create Event")
}
)
}
}
Three steps got this working for me : first add an #State Bool to track the showing of the new view :
#State var showNewView = false
Add the navigationBarItem, with an action that sets the above property :
.navigationBarItems(trailing:
Button(action: {
self.showNewView = true
}) {
Text("Go To Destination")
}
)
Finally add a navigation link somewhere in your view code (this relies on also having a NavigationView somewhere in the view stack)
NavigationLink(
destination: MyDestinationView(),
isActive: $showNewView
) {
EmptyView()
}.isDetailLink(false)
Put the NavigationLink into the label of a button.
.navigationBarItems(
trailing: Button(action: {}, label: {
NavigationLink(destination: NewView()) {
Text("")
}
}))
This works for me:
.navigationBarItems(trailing: HStack { AddButton(destination: EntityAddView()) ; EditButton() } )
Where:
struct AddButton<Destination : View>: View {
var destination: Destination
var body: some View {
NavigationLink(destination: self.destination) { Image(systemName: "plus") }
}
}
It is an iOS13 bug at the moment: https://forums.developer.apple.com/thread/124757
The "sort-of" workaround can be found here: https://stackoverflow.com/a/57837007/4514671
Here is my solution:
MasterView -
import SwiftUI
struct MasterView: View {
#State private var navigationSelectionTag: Int? = 0
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: DestinationView(), tag: 1, selection: self.$navigationSelectionTag) {
EmptyView()
}
Spacer()
}
.navigationBarTitle("Master")
.navigationBarItems(trailing: Button(action: {
self.navigationSelectionTag = 1
}, label: {
Image(systemName: "person.fill")
}))
}
}
}
struct MasterView_Previews: PreviewProvider {
static var previews: some View {
MasterView()
}
}
And the DetailsView -
import SwiftUI
struct DetailsView: View {
var body: some View {
Text("Hello, Details!")
}
}
struct DetailsView_Previews: PreviewProvider {
static var previews: some View {
DetailsView()
}
}