Why do I exit my TabView when navigating to a child view? - swiftui

Question. Why do I exit my TabView when navigating to a view that is a child of a TabView? I hope I am being clear, but the code below is ready to be copy and pasted and notice how when I navigate to UnrelatedView I exit my tabView...
Additional context: The root view ContentView in this case is embedded inside of a NavigationView in the App.swift file
import SwiftUI
struct ContentView: View {
var body: some View {
TabView {
ViewA()
.tabItem {
Text("A")
}
ViewB()
.tabItem {
Text("B")
}
ViewC()
.tabItem {
Text("C")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct ViewA: View {
var body: some View {
NavigationLink(destination: UnrelatedView()) {
Text("ViewA")
}
}
}
struct ViewB: View {
var body: some View {
Text("ViewB")
}
}
struct ViewC: View {
var body: some View {
Text("ViewC")
}
}
struct UnrelatedView: View {
var body: some View {
NavigationView {
Text("Unrelated View")
}
}
}

You need to wrap your navigation in NavigationViews.
struct ViewA: View {
var body: some View {
NavigationView {
NavigationLink(destination: UnrelatedView()) {
Text("ViewA")
}
}
}
}

Related

Trouble using #environmentObject and #StateObject

I am creating my first app and I am having trouble using #EnvironmentObject and #StateObject. When I run the simulator it opens my ContentView(). I am trying to get it to where it opens the MainView(), which has a tabView. And now the tabView is not showing my ContentView(). I am assuming I have the #EnvironmentObject and #StateObject in the wrong places. In my ContentView it shows a list and addButton. I am basically trying to have a program that is updated by the user by filling out a form.
Here is the Main Method
import SwiftUI
#main
struct Location_ScoutApp: App {
#StateObject var listViewModels: ListViewModel = ListViewModel()
var body: some Scene {
WindowGroup {
// MainView(){
ContentView()
.environmentObject(listViewModels)
//}
}
}
}
Here is my MainView.
import SwiftUI
struct MainView: View {
var body: some View {
TabView {
MapView()
.tabItem {
Label("Map", systemImage: "map.circle")
}
// this is where i am having trouble.
ContentView()
.tabItem {
Label("Explore", systemImage: "magnifyingglass")
}
ProfileView()
.tabItem {
Label("Profile", systemImage: "person.crop.circle")
}
}
}
}
struct MainView_Previews: PreviewProvider {
static var previews: some View {
MainView()
}
}
Here is my ContentView:
import SwiftUI
struct ContentView: View {
#EnvironmentObject var listViewModel: ListViewModel
var body: some View {
List {
ForEach(listViewModel.items) { item in
// this is where i am getting my new error.
ListRowView(item: item)
.onTapGesture {
listViewModel.updateItem(item: item)
}
}
}
.navigationTitle("Explore")
.navigationBarItems(
leading: EditButton(),
trailing:
NavigationLink("Add", destination: addALandmarkForm()))
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
ContentView()
}
.environmentObject(ListViewModel())
}
}
Without a reproducible example, it's hard to debug. This code, however, which is based on yours, works fine. Perhaps you can find the difference between your implementation and mine:
struct Item : Identifiable {
var id = UUID()
var title : String
}
class ListViewModel: ObservableObject {
#Published var items : [Item] = [.init(title: "Test 1"),.init(title: "Test 2")]
}
#main
struct Location_ScoutApp: App {
#StateObject var listViewModels: ListViewModel = ListViewModel()
var body: some Scene {
WindowGroup {
MainView()
.environmentObject(listViewModels)
}
}
}
struct MainView: View {
var body: some View {
TabView {
Text("Map")
.tabItem {
Label("Map", systemImage: "map.circle")
}
ContentView()
.tabItem {
Label("Explore", systemImage: "magnifyingglass")
}
Text("profile")
.tabItem {
Label("Profile", systemImage: "person.crop.circle")
}
}
}
}
struct ContentView: View {
#EnvironmentObject var listViewModel: ListViewModel
var body: some View {
NavigationView {
List {
ForEach(listViewModel.items) { item in
Text(item.title)
}
}
.navigationTitle("Explore")
}
}
}

How can I Hide TabBar in specific Views, in iOS 15 using SwiftUI

I need my TabBar to disappear if I click on a NavigationLink.
I know you can achieve that in iOS 14 with the following code:
NavigationView{
TabView{
View1().tabItem {
Image(systemName: "house.fill")
Text("Home")
}
}
}
And View1:
struct View1: some View {
var body: some View {
NavigationView{
NavigationLink(destination: Text("New Page without the Tabbar")) {
Text("Link")
}
}
}
}
But somehow this don't works in iOS 15...
Are there any other workarounds?
You could try using only one NavigationView, like in this example:
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
TabView {
View1().tabItem {
Image(systemName: "house.fill")
Text("Home")
}
}
}
}
}
struct View1: View {
var body: some View {
// ---> here no NavigationView
NavigationLink(destination: Text("New Page without the Tabbar")) {
Text("Link")
}
}
}

SwiftUI - How to add toolbars to TabView tabs inside a NavigationView?

I'm trying to add different toolbars to each of my tabs but they are not displayed. The app will mostly be used on a landscape iPad and I can add the toolbars to the TabView itself and they display but then I don't know how to pass the button press down the navigation stack to the individual views/view-models to be handled locally.
I've tried adding new NavigationViews (including .stack navigationViewStyles) but this still just adds another column to the view.
This is some barebones, working code:
import SwiftUI
#main
struct NavTabTestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
var body: some View {
MasterView()
}
}
struct MasterView: View {
var body: some View {
NavigationView {
List {
ForEach(0..<20) { index in
NavigationLink(
destination: DetailView(index: index)
.navigationTitle("Row \(index)")
) {
Text("\(index) th row")
}.tag(index)
}
}.navigationTitle(Text("Ratty"))
}
}
}
struct DetailView: View {
var index: Int
#State var selectedTab = 1
var body: some View {
TabView(selection: $selectedTab) {
Tab1(index: index).tabItem { Label("Tab1", systemImage: "list.dash") }
Tab2(index: index).tabItem { Label("Tab2", systemImage: "aqi.medium") }
Tab3(index: index).tabItem { Label("Tab3", systemImage: "move.3d") }
}
}
}
struct Tab1: View {
var index: Int
var body: some View {
Text("This is \(index) in tab 1")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button("Bingo") { print("Bingo") }
}
}
}
}
struct Tab2: View {
var index: Int
var body: some View {
Text("This is \(index) in tab 2")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button("Bongo") { print("Bongo") }
}
}
}
}
struct Tab3: View {
var index: Int
var body: some View {
Text("This is \(index) in tab 3")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button("Banjo") { print("Banjo") }
}
}
}
}
I'm starting to wonder if this is even possible and whether it would be better to just implement my own view with buttons at the top of each tab.
EDIT:
Not sure if this will help but it does go over some interesting concepts with the toolbar in the nav view.
link: Stewart Lynch
You need to use only one level NavigationView. In other words, you should not nest NavigationViews. Have a look at this answer.
Only Back Button Visible on Custom Navigation Bar SwiftUI
Use a NavgationView for the MasterView as you have used.
Use a NavigationView for each of the Tab# veiws.
Switch between MasterView and DetailsView.
Use Button instead of NavigationLink in MasterView (You can customise it to look like a NavigationLink)
Use a custom back button in each of the Tab# veiws.
This controls which one of MasterView and DetailsView should be shown.
class BaseViewModel: ObservableObject {
#Published var userFlow: UserFlow = .masterView
init(){
userFlow = .masterView
}
enum UserFlow {
case masterView, detailsView
}
}
This view to be used either in ContentView or instead of it. When you are in MasterView and click on one of the list row, set appState.userFlow = .detailView. When you click the back buttons, set appState.userFlow = .masterView.
Doing so, you switch between the two views and one NavigationView is shown at a time.
As of now, it does not have animation. Use if you wish so
https://developer.apple.com/tutorials/swiftui/animating-views-and-transitions
struct BaseView: View {
#EnvironmentObject var appState: BaseViewModel
#State var index: Int = 0
var body: some View {
Group {
switch appState.userFlow {
case .masterView:
MasterView(index: $index)
default:
DetailView(index: index)
}
}
.edgesIgnoringSafeArea(.bottom)
}
}
Complete code
struct ContentView: View {
var body: some View {
BaseView().environmentObject(BaseViewModel())
}
}
class BaseViewModel: ObservableObject {
#Published var userFlow: UserFlow = .masterView
init(){
userFlow = .masterView
}
enum UserFlow {
case masterView, detailsView
}
}
struct BaseView: View {
#EnvironmentObject var appState: BaseViewModel
#State var index: Int = 0
var body: some View {
Group {
switch appState.userFlow {
case .masterView:
MasterView(index: $index)
default:
DetailView(index: index)
}
}
.edgesIgnoringSafeArea(.bottom)
}
}
struct MasterView: View {
#EnvironmentObject var appState: BaseViewModel
#Binding var index: Int
var body: some View {
NavigationView {
List {
ForEach(0..<20) { index in
Button(action: {
appState.userFlow = .detailsView
$index.wrappedValue = index
}, label: {
HStack {
Text("\(index) th row")
Spacer()
Image(systemName: "greaterthan")
}
})
.tag(index)
}
}.navigationTitle(Text("Ratty"))
}
}
}
struct DetailView: View {
var index: Int
#State var selectedTab = 1
var body: some View {
TabView(selection: $selectedTab) {
Tab1(index: index).tabItem { Label("Tab1", systemImage: "list.dash") }
Tab2(index: index).tabItem { Label("Tab2", systemImage: "aqi.medium") }
Tab3(index: index).tabItem { Label("Tab3", systemImage: "move.3d") }
}
}
}
struct Tab1: View {
#EnvironmentObject var appState: BaseViewModel
var index: Int
var body: some View {
NavigationView {
Text("This is \(index) in tab 1")
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button("back") { appState.userFlow = .masterView }
}
ToolbarItem(placement: .navigationBarTrailing) {
Button("Bingo") { print("Bingo") }
}
}
}
}
}
struct Tab2: View {
#EnvironmentObject var appState: BaseViewModel
var index: Int
var body: some View {
NavigationView {
Text("This is \(index) in tab 2")
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button("back") { appState.userFlow = .masterView }
}
ToolbarItem(placement: .primaryAction) {
Button("Bongo") { print("Bongo") }
}
}
}
}
}
struct Tab3: View {
#EnvironmentObject var appState: BaseViewModel
var index: Int
var body: some View {
NavigationView {
Text("This is \(index) in tab 3")
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button("back") { appState.userFlow = .masterView }
}
ToolbarItem(placement: .primaryAction) {
Button("Banjo") { print("Banjo") }
}
}
}
}
}
I have customised xtwistedx 's solution.

How to get a value as to which view is selected in TabView?

I'm currently developing an application using SwiftUI.
This app has 2 Views controlled a Tab View.
I want to get a value as to which view is selected in TabView.
Is there any way to do that?
ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
TabView {
FirstView()
.tabItem {
Text("First")
}.tag(1)
SecondView()
.tabItem {
Text("Second")
}.tag(2)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
FirstView.swift
import SwiftUI
struct FirstView: View {
var body: some View {
Text("FirstView")
}
}
struct FirstView_Previews: PreviewProvider {
static var previews: some View {
FirstView()
}
}
SecondView.swift
import SwiftUI
struct SecondView: View {
var body: some View {
Text("SecondView")
}
}
struct SecondView_Previews: PreviewProvider {
static var previews: some View {
SecondView()
}
}
Xcode: Version 11.7
Swift: Swift 5
You need to use selection, like below
Note: selection should be same type as used for tags (and use corresponding values from tags to select specific tab programmatically)
struct ContentView: View {
#State private var selectedTab = 1 // default selection
var body: some View {
TabView(selection: $selectedTab) { // << here !!
FirstView()
.tabItem {
Text("First")
}.tag(1)
SecondView()
.tabItem {
Text("Second")
}.tag(2)
}
}
}

HStack NavigationLink in Form tappable area small

I would like to be able to tap the entire row in this form to navigate to the next view; what am I doing wrong? It only allows me to navigate if I tap the symbol at the end.
This code should compile and run in your simulator.
Thanks.
import SwiftUI
struct SwiftUIView: View {
let selectedTags = ["A", "B", "C"]
var body: some View {
NavigationView {
Form {
NavigationLink(destination: DetailView()) {
ScrollView(.horizontal) {
HStack {
ForEach(selectedTags, id: \.self) { tag in
Text(tag)
}
}
}
}
}
}
}
}
struct DetailView: View {
var body: some View {
Text("Welcome")
}
}
struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
SwiftUIView()
}
}
copy - paste - run
import SwiftUI
struct ContentView: View {
let selectedTags = ["A", "B", "C"]
#State var active = false
var body: some View {
NavigationView {
Form {
NavigationLink( destination: DetailView(), isActive: $active) {
ScrollView(.horizontal) {
HStack {
ForEach(selectedTags, id: \.self) { tag in
Text(tag)
}
}
}.onTapGesture {
self.active.toggle()
}
}
}
}
}
}
struct DetailView: View {
var body: some View {
Text("Welcome")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}