Custom back button for NavigationView's navigation bar in SwiftUI
TL;DR
Use this to transition to your view:
NavigationLink(destination: SampleDetails()) {}
Add this to the view itself:
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
Then, in a button action or something, dismiss the view:
presentationMode.wrappedValue.dismiss()
Full code
From a parent, navigate using NavigationLink
NavigationLink(destination: SampleDetails()) {}
In DetailsView hide navigationBarBackButton
and set custom back button to leading navigationBarItem
,
struct SampleDetails: View {
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var btnBack : some View { Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
HStack {
Image("ic_back") // set image here
.aspectRatio(contentMode: .fit)
.foregroundColor(.white)
Text("Go back")
}
}
}
var body: some View {
List {
Text("sample code")
}
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: btnBack)
}
}
Only Back Button Visible on Custom Navigation Bar SwiftUI
I want to share a structure with you that will solve all of your woes. If I'm understanding you correctly, you have multiple navigation flows, eg. Login, Home, some other flow. Which is causing you to have NavigationView { Navigation View { NavigationView }}}
nesting going on, with multiple back buttons.
Here is a possible solution, and frankly it'll help a lot more in the future with other projects.
Base View Model
This view model is to control a Base View
which is essentially a Navigation control view.
class BaseViewModel: ObservableObject {
@Published var userFlow: UserFlow = .loading
init(){
//You might check for login state here
//IF logged in, then go to home, otherwise
//go back to login.
userFlow = .home
}
enum UserFlow {
case loading, onboarding, login, home
}
}
Base View
This BaseView
will update whenever the BaseViewModel
environment object is changed. It's bound, so when it changes, the user flow will change too. This will allow you to have multiple navigation stacks on a per-flow basis. In other words create one flow for login, another for logged-in, and any other for whatever you need, the navigation views will no longer interfere with each other.
struct BaseView: View {
//We use an @EnvironmentObject here because later on
//in the app we access this and change the state
//so that the BaseView updates it's flow.
@EnvironmentObject var appState: BaseViewModel
var body: some View {
//Make sure to group, or it won't work properly.
Group {
switch appState.userFlow {
case .onboarding:
Text("Not Yet Implemented")
case .login:
LandingPageView()
case .home:
BaseHomeScreenView().environmentObject(BaseHomeScreenViewModel())
case .loading:
LoadingView()
}
}
.edgesIgnoringSafeArea(.bottom)
.transition(.opacity)
.animation(.spring())
}
}
Usage
Simply grab the created @EnvironmentObject
and set its value. Swift will take over from there and swap your views using the switch appState.userFlow
located in the BaseView
struct View1: View {
@EnvironmentObject var userFlow: BaseViewModel
var body: some View {
VStack {
Button(action: {userFlow = .login}, label: {
Text("Go to login")
})
}
}
}
Note I did this without my IDE, forgive any syntax errors.
How to bind an action to the navigationview back button?
You can't bind directly to the back button, but you can have the navigation link itself be activated based on state, and then listen to the change of the state value like so. Do note that this requires that you manage the setting of state to true (no auto tap like with the default initializer)
struct ContentView: View {
@State private var showingNavView = false
var body: some View {
NavigationView {
List {
NavigationLink("Sub View", isActive: $showingNavView) {
SubView()
}.onTapGesture {
showingNavView = true
}.onChange(of: showingNavView) { newValue in
print(newValue) // Will change to false when back is pressed
}
}
}
}
}
struct SubView: View {
var body: some View {
ZStack {
Color.green
Text("Cool Beans")
}
}
}
Default text for back button in NavigationView in SwiftUI
I've managed to localize back buttons by providing translations for the Back
key in the Localizable.strings
file.
I am using SwiftUI though.
NavigationBar with Custom Back Text but no NavigationBarTitle
You can achieve what you need by using the environment value of presentationMode to dismiss the screen you are in by code, and for changing the back button label and style you can simply do
- hide the back button by using
.navigationBarBackButtonHidden(true)
modifier - use
toolbar
andtoolbarItem
to add your custom back button to the NavigationBar
here an example of how you can use it
// FirstScreen
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink {
SecondScreen()
} label: {
Text("Go To Second Screen")
}
}
}
}
// SecondScreen
struct SecondScreen: View {
@Environment(\.presentationMode) var presentationMode
var body: some View {
Text("Second Screen")
.navigationBarBackButtonHidden(true)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
presentationMode.wrappedValue.dismiss()
} label: {
Label("Custom Back Button", systemImage: "command")
.labelStyle(.titleAndIcon)
}
}
}
}
}
Combine NavigationTitle and Navigation Back Button SwiftUI
jnpdx solved this... the solution was that I had an extra NavigationView
in my SettingsView. I only needed one NavigationView
.
NavigationBar with Custom Back Text but no NavigationBarTitle
You can achieve what you need by using the environment value of presentationMode to dismiss the screen you are in by code, and for changing the back button label and style you can simply do
- hide the back button by using
.navigationBarBackButtonHidden(true)
modifier - use
toolbar
andtoolbarItem
to add your custom back button to the NavigationBar
here an example of how you can use it
// FirstScreen
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink {
SecondScreen()
} label: {
Text("Go To Second Screen")
}
}
}
}
// SecondScreen
struct SecondScreen: View {
@Environment(\.presentationMode) var presentationMode
var body: some View {
Text("Second Screen")
.navigationBarBackButtonHidden(true)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
presentationMode.wrappedValue.dismiss()
} label: {
Label("Custom Back Button", systemImage: "command")
.labelStyle(.titleAndIcon)
}
}
}
}
}
Related Topics
Updating the Ui Using Dispatch_Async in Swift
Swift @Escaping and Completion Handler
Changing Text Color of Datepicker
How to Copy End of the Array in Swift
Make a Swift Dictionary Where the Key Is "Type"
Swift 3.0: Convert Server Utc Time to Local Time and Vice-Versa
Segue from Modal View to Tab Bar View Controller and Not Lose Tab Bar
Do Swift-Based Applications Work on Os X 10.9/Ios 7 and Lower
Usb Connection Delegate on Swift
Computed Read-Only Property VS Function in Swift
How to Convert Array of Bytes [Uint8] into Hexa String in Swift
Swift Random Float Between 0 and 1
Get Associated Value from Enumeration Without Switch/Case
What Is Difference Between Optional and Decodeifpresent When Using Decodable For Json Parsing
Generic Function Taking a Type Name in Swift
How to Improve People Occlusion in Arkit 3.0
Deletable Table With Textfield on Swiftui
How to Determine If a String Contains a Character from a Set in Swift