I have a basic tabview with 2 tabs. Tab 2 has a button to a modal sheet with a Page Style tabview imbedded in a Navigation Stack and added toolbar. When adding the Navigation Stack I get an unwanted white space at the bottom of the sheet view pages. I have tried using .ignoreSafeArea(edges: .bottom) in many places no solution and I'm stumped on this one. What am I missing here? How do I get rid of this unwanted white space? Is my navigation stack in the wrong place? Seems like such a simple design to be such a problem.
iOS 16.1
Xcode 14.2
struct HomeView: View {
#State private var pageIndex = 0
var body: some View {
TabView(selection: $pageIndex) {
PageOne()
.tabItem {
Label("Page 1", systemImage: "star")
}.tag(0)
PageTwo()
.tabItem {
Label("Page 2", systemImage: "bookmark")
}.tag(1)
}
}
}
struct PageOne: View {
var body: some View {
VStack {
Text("Page 1")
}
}
}
struct PageTwo: View {
#State private var sheetIsShowing = false
var body: some View {
VStack {
Text("Page 2")
Button("Show Sheet") {
self.sheetIsShowing.toggle()
}
}
.fullScreenCover(isPresented: $sheetIsShowing) {
SheetView(sheetIsShowing: $sheetIsShowing)
}
}
}
struct SheetView: View {
#Binding var sheetIsShowing: Bool
var body: some View {
NavigationStack {
TabView {
SheetTabView1()
SheetTabView2()
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
self.sheetIsShowing.toggle()
} label: {
Text("Cancel")
}
}
}
}
.ignoresSafeArea(edges: .bottom)
}
}
struct SheetTabView1: View {
#State private var text1 = ""
#State private var text2 = ""
var body: some View {
List {
TextField("Enter Text", text: $text1)
TextField("Enter Text", text: $text2)
}
}
}
struct SheetTabView2: View {
#State private var text1 = ""
#State private var text2 = ""
var body: some View {
List {
TextField("Enter Text", text: $text1)
TextField("Enter Text", text: $text2)
}
}
}
The problem here is the tabViewStyle view modifier. If you get rid of it, you'll get rid of the white space.
I am trying to build a app. I am using presentation mode to show the view, then I switch to another but when I use the dismiss function it just takes me back to the first view.
struct ContentView: View {
#Environment(\.presentationMode) var presentationMode
#State private var isShowingBookingScreen:Bool = false
var body: some View {
Button {
isShowingBookingScreen = true
} label: {
Spacer()
Text("Book Now").foregroundColor(.white).padding().font(.headline)
Spacer()
}.sheet(isPresented: $isShowingBookingScreen) {
SecondView()
}.background(Color.accentColor).cornerRadius(10).padding()
}
}
struct SecondView: View {
var body: some View {
NavigationView{
NavigationLink(destination:ThirdView()) {
Text("Continue to Third").foregroundColor(.red).padding().cornerRadius(10).padding()
}
}
}
}
struct ThirdView: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
Button {
presentationMode.wrappedValue.dismiss()
} label: {
Text("Dismiss")
}
}
}
I have lately been trying to make a tvOS app, but have run into the following rather annoying problem. I can't use navigation inside a TabView and still have the menu button on the remove take me back to the previous state.
struct TestView: View {
#State var selection : Int = 0
var body: some View {
TabView(selection: self.$selection) {
ExpView()
.tabItem {
HStack {
Image(systemName: "magnifyingglass")
Text("Explore")
}
}
.tag(0)
}
}
}
struct ExpView: View {
var body: some View {
NavigationView {
NavigationLink(destination: DetailView(title: "Hey")) {
Text("Detail")
}
}
}
}
struct DetailView: View {
var title : String
var body: some View {
VStack {
Text(title)
}
}
}
My question is: Is there any way to enable the menu button to go back to the previous view in the hierachy without dismissing the app completely?
You don't need to call dismiss on Menu it is called automatically for NavigationLink (so calling one more dismiss quits to main menu)
Here are fixed views. Tested with Xcode 11.4
struct ExploreView: View {
var body: some View {
NavigationView {
NavigationLink(destination: DetailView(title: "Hey")) {
Text("Detail")
}
}
}
}
struct DetailView: View {
var title : String
var body: some View {
VStack {
Text(title)
}
}
}
So I found a workaround for the issue.
If you place the navigationView outside the TabView and then use the following code it works:
struct TestView: View {
#State var selection : Int = 0
#State var hideNavigationBar : Bool
var body: some View {
NavigationView {
TabView(selection: self.$selection) {
ExpView(hideNavigationBar: self.$hideNavigationBar)
.tabItem {
HStack {
Image(systemName: "magnifyingglass")
Text("Explore")
}
}
.tag(0)
}
}
}
}
struct ExpView: View {
#Binding var hideNavigationBar : Bool
var body: some View {
NavigationLink(destination: DetailView(title: "Hey")) {
Text("Detail")
}.navigationBarTitle("")
.navigationBarHidden(self.hideNavigationBar)
.onAppear {
self.hideNavigationBar = true
}
}
}
struct DetailView: View {
var title : String
var body: some View {
VStack {
Text(title)
}
}
}
I have a Content file and am hiding the navigation bar because it takes up space and pushes elements down. One of the buttons in the ContentView redirects (using a navigation link) to another view. In this other view, the navigationBar is still hidden....for simplicity sake, I'll cut out some of the code from ContentView:
//this is the view that looks "fine" (i.e. the navigation bar takes up no space)
struct ContentView: View {
#State private var isPresentedSettings = false
var body: some View {
NavigationView {
ZStack {
VStack {
SettingsButton(isPresentedSettings: $isPresentedSettings)
}
}.navigationBarTitle("").navigationBarHidden(true)
}
}
}
//this is the button that pulls up the settings page view
struct SettingsButton: View {
#Binding var isPresentedSettings: Bool
var body: some View {
NavigationLink (destination: SettingsPageView(isPresentedSettings:
self.$isPresentedSettings)) {
Button(action: { self.isPresentedSettings.toggle() }, label: { Text("Button") })
}
}
}
//This is the view that should have a navigationbar but it doesn't
struct SettingsPageView: View {
#Binding var isPresentedSettings: Bool
var body: some View {
NavigationView {
VStack {
Text("This is a view")
}.navigationBarTitle("Settings", displayMode: .inline)
}
}
}
Also...there may have been typos because I just copied the code over from another computer. Sorry and thank you in advance!
Firstly, you don't need to have this isPresentedSettings variable for presenting a NavigationLink.
NavigationLink(destination: SettingsPageView()) {
Text("Button")
}
And there should be only one NavigationView in your view hierarchy.
This is how your final code can look like:
struct ContentView: View {
#State private var navBarHidden = true
var body: some View {
NavigationView {
ZStack {
VStack {
SettingsButton(navBarHidden: $navBarHidden)
}
}
.navigationBarHidden(navBarHidden)
}
}
}
struct SettingsButton: View {
#Binding var navBarHidden: Bool
var body: some View {
NavigationLink(destination: SettingsPageView(navBarHidden: $navBarHidden)) {
Text("Show View")
}
}
}
struct SettingsPageView: View {
#Binding var navBarHidden: Bool
var body: some View {
VStack {
Text("This is a view")
}
.navigationBarTitle("Settings", displayMode: .inline)
.onAppear {
self.navBarHidden = false
}
.onDisappear {
self.navBarHidden = true
}
}
}
I was playing around with SwiftUI and want to be able to come back to the previous view when tapping a button, the same we use popViewController inside a UINavigationController.
Is there a provided way to do it so far ?
I've also tried to use NavigationDestinationLink to do so without success.
struct AView: View {
var body: some View {
NavigationView {
NavigationButton(destination: BView()) {
Text("Go to B")
}
}
}
}
struct BView: View {
var body: some View {
Button(action: {
// Trying to go back to the previous view
// previously: navigationController.popViewController(animated: true)
}) {
Text("Come back to A")
}
}
}
Modify your BView struct as follows. The button will perform just as popViewController did in UIKit.
struct BView: View {
#Environment(\.presentationMode) var mode: Binding<PresentationMode>
var body: some View {
Button(action: { self.mode.wrappedValue.dismiss() })
{ Text("Come back to A") }
}
}
Use #Environment(\.presentationMode) var presentationMode to go back previous view. Check below code for more understanding.
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
ZStack {
Color.gray.opacity(0.2)
NavigationLink(destination: NextView(), label: {Text("Go to Next View").font(.largeTitle)})
}.navigationBarTitle(Text("This is Navigation"), displayMode: .large)
.edgesIgnoringSafeArea(.bottom)
}
}
}
struct NextView: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
ZStack {
Color.gray.opacity(0.2)
}.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: Button(action: {
self.presentationMode.wrappedValue.dismiss()
}, label: { Image(systemName: "arrow.left") }))
.navigationBarTitle("", displayMode: .inline)
}
}
struct NameRow: View {
var name: String
var body: some View {
HStack {
Image(systemName: "circle.fill").foregroundColor(Color.green)
Text(name)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
With State Variables. Try that.
struct ContentViewRoot: View {
#State var pushed: Bool = false
var body: some View {
NavigationView{
VStack{
NavigationLink(destination:ContentViewFirst(pushed: self.$pushed), isActive: self.$pushed) { EmptyView() }
.navigationBarTitle("Root")
Button("push"){
self.pushed = true
}
}
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
struct ContentViewFirst: View {
#Binding var pushed: Bool
#State var secondPushed: Bool = false
var body: some View {
VStack{
NavigationLink(destination: ContentViewSecond(pushed: self.$pushed, secondPushed: self.$secondPushed), isActive: self.$secondPushed) { EmptyView() }
.navigationBarTitle("1st")
Button("push"){
self.secondPushed = true;
}
}
}
}
struct ContentViewSecond: View {
#Binding var pushed: Bool
#Binding var secondPushed: Bool
var body: some View {
VStack{
Spacer()
Button("PopToRoot"){
self.pushed = false
} .navigationBarTitle("2st")
Spacer()
Button("Pop"){
self.secondPushed = false
} .navigationBarTitle("1st")
Spacer()
}
}
}
This seems to work for me on watchOS (haven't tried on iOS):
#Environment(\.presentationMode) var presentationMode
And then when you need to pop
self.presentationMode.wrappedValue.dismiss()
There is now a way to programmatically pop in a NavigationView, if you would like. This is in beta 5.
Notice that you don't need the back button. You could programmatically trigger the showSelf property in the DetailView any way you like. And you don't have to display the "Push" text in the master. That could be an EmptyView(), thereby creating an invisible segue.
(The new NavigationLink functionality takes over the deprecated NavigationDestinationLink)
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
MasterView()
}
}
}
struct MasterView: View {
#State var showDetail = false
var body: some View {
VStack {
NavigationLink(destination: DetailView(showSelf: $showDetail), isActive: $showDetail) {
Text("Push")
}
}
}
}
struct DetailView: View {
#Binding var showSelf: Bool
var body: some View {
Button(action: {
self.showSelf = false
}) {
Text("Pop")
}
}
}
#if DEBUG
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
#endif
It seems that a ton of basic navigation functionality is super buggy, which is disappointing and may be worth walking away from for now to save hours of frustration. For me, PresentationButton is the only one that works. TabbedView tabs don't work properly, and NavigationButton doesn't work for me at all. Sounds like YMMV if NavigationButton works for you.
I'm hoping that they fix it at the same time they fix autocomplete, which would give us much better insight as to what is available to us. In the meantime, I'm reluctantly coding around it and keeping notes for when fixes come out. It sucks to have to figure out if we're doing something wrong or if it just doesn't work, but that's beta for you!
Update: the NavigationDestinationLink API in this solution has been deprecated as of iOS 13 Beta 5. It is now recommended to use NavigationLink with an isActive binding.
I figured out a solution for programmatic pushing/popping of views in a NavigationView using NavigationDestinationLink.
Here's a simple example:
import Combine
import SwiftUI
struct DetailView: View {
var onDismiss: () -> Void
var body: some View {
Button(
"Here are details. Tap to go back.",
action: self.onDismiss
)
}
}
struct MainView: View {
var link: NavigationDestinationLink<DetailView>
var publisher: AnyPublisher<Void, Never>
init() {
let publisher = PassthroughSubject<Void, Never>()
self.link = NavigationDestinationLink(
DetailView(onDismiss: { publisher.send() }),
isDetail: false
)
self.publisher = publisher.eraseToAnyPublisher()
}
var body: some View {
VStack {
Button("I am root. Tap for more details.", action: {
self.link.presented?.value = true
})
}
.onReceive(publisher, perform: { _ in
self.link.presented?.value = false
})
}
}
struct RootView: View {
var body: some View {
NavigationView {
MainView()
}
}
}
I wrote about this in a blog post here.
You can also do it with .sheet
.navigationBarItems(trailing: Button(action: {
self.presentingEditView.toggle()
}) {
Image(systemName: "square.and.pencil")
}.sheet(isPresented: $presentingEditView) {
EditItemView()
})
In my case I use it from a right navigation bar item, then you have to create the view (EditItemView() in my case) that you are going to display in that modal view.
https://developer.apple.com/documentation/swiftui/view/sheet(ispresented:ondismiss:content:)
EDIT: This answer over here is better than mine, but both work: SwiftUI dismiss modal
What you really want (or should want) is a modal presentation, which several people have mentioned here. If you go that path, you definitely will need to be able to programmatically dismiss the modal, and Erica Sadun has a great example of how to do that here: https://ericasadun.com/2019/06/16/swiftui-modal-presentation/
Given the difference between declarative coding and imperative coding, the solution there may be non-obvious (toggling a bool to false to dismiss the modal, for example), but it makes sense if your model state is the source of truth, rather than the state of the UI itself.
Here's my quick take on Erica's example, using a binding passed into the TestModal so that it can dismiss itself without having to be a member of the ContentView itself (as Erica's is, for simplicity).
struct TestModal: View {
#State var isPresented: Binding<Bool>
var body: some View {
Button(action: { self.isPresented.value = false }, label: { Text("Done") })
}
}
struct ContentView : View {
#State var modalPresented = false
var body: some View {
NavigationView {
Text("Hello World")
.navigationBarTitle(Text("View"))
.navigationBarItems(trailing:
Button(action: { self.modalPresented = true }) { Text("Show Modal") })
}
.presentation(self.modalPresented ? Modal(TestModal(isPresented: $modalPresented)) {
self.modalPresented.toggle()
} : nil)
}
}
Below works for me in XCode11 GM
self.myPresentationMode.wrappedValue.dismiss()
instead of NavigationButton use Navigation DestinationLink
but You should import Combine
struct AView: View {
var link: NavigationDestinationLink<BView>
var publisher: AnyPublisher<Void, Never>
init() {
let publisher = PassthroughSubject<Void, Never>()
self.link = NavigationDestinationLink(
BView(onDismiss: { publisher.send() }),
isDetail: false
)
self.publisher = publisher.eraseToAnyPublisher()
}
var body: some View {
NavigationView {
Button(action:{
self.link.presented?.value = true
}) {
Text("Go to B")
}.onReceive(publisher, perform: { _ in
self.link.presented?.value = false
})
}
}
}
struct BView: View {
var onDismiss: () -> Void
var body: some View {
Button(action: self.onDismiss) {
Text("Come back to A")
}
}
}
In the destination pass the view you want to redirect, and inside block pass data you to pass in another view.
NavigationLink(destination: "Pass the particuter View") {
Text("Push")
}