Default text for back button in NavigationView in SwiftUI - ios

I use a NavigationLink to navigate from "View1" to "View2", on the second view, the back button gets the title of the previous view
But, if the title of the previous view is very long, then the back button gets the text "Back"
How could I change that "Back" text?
I wanna make my app available in multiple languages, but it seems that "Back" does not change when phone's language changes
struct ContentView: View {
var body: some View {
return NavigationView {
VStack {
Text("View1")
NavigationLink(destination: Text("View2").navigationBarTitle("Title View2", displayMode: .inline)) {
Text("NavigationLink")
}
}.navigationBarTitle("Title View1")
}
}
}
PS: I'd like to keep this functionality as it it, I just want to change the language used for back button

here is a workaround ....
struct ContentView: View {
#State private var isActive: Bool = false
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: DetailView(), isActive: $isActive) {
Text("Title View2")
}
}.navigationBarTitle(! isActive ? "Title View2" : "Your desired back Title", displayMode: .inline)
}
}
}
struct DetailView: View {
var body: some View {
Text("View2")
}
}

I found a solution which can work also very good.
View1 set a toolbar item with .principal and add your Text or what you want.
for example :
ToolbarItem(placement: .principal) {
HStack{
Text("View1")
}.font(.subheadline)
}
and set also your title in View1:
.navigationTitle("Back")
And do nothing in your view2. it will automatically add your view1 title to your view2 default back button text

I've managed to localize back buttons by providing translations for the Back key in the Localizable.strings file.
I am using SwiftUI though.

You can create a custom back button in your navigation link by hiding native navigationBackButton. In the custom back button, you can add your translated custom back button title.
struct ContentView: View {
var body: some View {
return NavigationView {
VStack {
Text("View1")
NavigationLink("NavigationLink", destination: NextView())
}.navigationBarTitle("Title View1")
}
}
}
struct NextView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var backButton : some View { Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
HStack {
Image("backImage") // BackButton Image
.aspectRatio(contentMode: .fit)
.foregroundColor(.white)
Text("Go Back") //translated Back button title
}
}
}
var body: some View {
VStack {
Text("View2")
}
.navigationBarTitle("Title View2",displayMode: .inline)
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: backButton)
}
}
Output:-

Create your own button, then assign it using .navigationBarItems(). I found the following format most nearly approximated the default back button.
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var backButton : some View {
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
HStack(spacing: 0) {
Image(systemName: "chevron.left")
.font(.title2)
Text("Cancel")
}
}
}
Make sure you use .navigationBarBackButtonHidden(true) to hide the default button and replace it with your own!
List(series, id:\.self, selection: $selection) { series in
Text(series.SeriesLabel)
}
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: backButton)

Add localization to your project. If language was set with user device settings(or simulator), after you add localization to your project it will work. Project's supported language must match with selected one on device.

Related

Swift UI Clicking navigation bar link hides status bar on back

I wrote a simple Swift UI app that creates a NavigationLink on the navigation toolbar and creates a status bar at the bottom of the display. When clicking on the gear link on the navigation bar, it takes you to the child view, but when you return to the parent view, the status bar gets hidden. If you click on the NavigationLink in the middle of the screen and return to the parent view, the status bar gets displayed again.
This looks like a bug in Swift UI and does anyone know how to fix?
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(destination: Text("Child view")) {
Text("Hello, World!")
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing, content: {
NavigationLink(destination: Text("Settings view"),
label: { Image(systemName: "gearshape.fill")
})
})
ToolbarItem(placement: .status, content: {
Text("Checking for messages...")
})
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The issue is that Swift UI doesn't handle NavigationLink properly inside a toolbar.
The workaround is to place a Button into a toolbar and use a hidden NavigationLink in the code.
This is a link to the answer that resolved my issue.
SwiftUI - Make toolbar's NavigationLink use detail view
Here is the code that implements my original code with the workaround.
struct ContentView: View {
#State var settingsLinkSelected = false
var body: some View {
NavigationView {
NavigationLink(destination: Text("Second view")) {
Text("Hello, World!")
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing,
content: { Button(action: { settingsLinkSelected = true },
label: { Image(systemName: "gearshape.fill") }) })
ToolbarItem(placement: .status,
content: { Text("Checking for messages...") })
}
.background(
NavigationLink(
destination: Text("Settings View"),
isActive: $settingsLinkSelected
) {
EmptyView()
}.hidden()
)
}
}
}

SwiftUI disappear back button with navigationLink

I have 3 views. One of these have NavigationView second have NavigationLink and last just a child with toolbar.
So my problem when I added toolbar in last view backButton elegant disappear. How can I solve this?
Screen recording of my problem
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
Text("Hello, world!")
.padding()
NavigationLink(destination: ListView()) {
Image(systemName: "trash")
.font(.largeTitle)
.foregroundColor(.red)
}
}.navigationBarHidden(true)
.navigationTitle("Image")
}
}
}
import SwiftUI
struct ListView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
VStack {
List {
NavigationLink(destination: DetailView()) {
Text("Detail")
}
}
}.navigationBarTitle(Text("Data"), displayMode: .large)
.toolbar {
Button("Save") {
presentationMode.wrappedValue.dismiss()
}
}
}
}
import SwiftUI
struct DetailView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
VStack {
Text("DetailView")
.padding()
}.navigationBarTitle(Text("Data"), displayMode: .large)
.toolbar {
Button("Save") {
presentationMode.wrappedValue.dismiss()
}
}
}
}
In the console, you'll notice this message:
2021-04-27 12:37:36.862733-0700 MyApp[12739:255441] [Assert] displayModeButtonItem is internally managed and not exposed for DoubleColumn style. Returning an empty, disconnected UIBarButtonItem to fulfill the non-null contract.
The default style for NavigationView is usually DefaultNavigationViewStyle, which is really just DoubleColumnNavigationViewStyle. Use StackNavigationViewStyle instead, and it works as expected.
Edit: You are right that StackNavigationViewStyle will break iPad split view. But thankfully, DoubleColumnNavigationViewStyle works fine in iPad and doesn't hide the back button. We can then just use a different NavigationStyle depending on the device, as shown in this answer.
struct ResponsiveNavigationStyle: ViewModifier {
#Environment(\.horizontalSizeClass) var horizontalSizeClass
#ViewBuilder
func body(content: Content) -> some View {
if horizontalSizeClass == .compact { /// iPhone
content.navigationViewStyle(StackNavigationViewStyle())
} else { /// iPad or larger iPhone in landscape
content.navigationViewStyle(DoubleColumnNavigationViewStyle())
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
Text("Hello, world!")
.padding()
NavigationLink(destination: ListView()) {
Image(systemName: "trash")
.font(.largeTitle)
.foregroundColor(.red)
}
}
.navigationBarHidden(true)
.navigationTitle("Image")
}
.modifier(ResponsiveNavigationStyle()) /// here!
}
}
Result:
iPad
iPhone
I don't know why, but it's what worked for me:
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button { } label: { } // button to the right
}
ToolbarItem(placement: .navigationBarLeading) {
Text("") // empty text in left to prevent back button to disappear
}
}
I already tried to replace the empty text with EmptyView() but the button keeps disappearing.
FYI: I have this problem only on my device with iOS 14, but in another device with iOS 15 the back button never disappears.

SwiftUI Show navigation bar title on the back button but not in the previous View

I have two views, one leads to the other. I want that the second view uses the title of the first view for the back button, which should then be: "<View1".
I don't want to show the title in the first view.
Problem: I can't hide navigation bar because it will also hide a custom button which is within it. Setting .navigationTitle("") hides the title in the first view, but also hides it from the back button in the second view.
What I have now:
What I would like to have:
Code:
struct ContentView: View {
#State var isLinkActive = false
var body: some View {
NavigationView {
VStack {
NavigationLink("go to the second view", destination: SecondView(), isActive: $isLinkActive).navigationTitle("View1")
.navigationBarItems(leading: Button(action: {
()
}, label: {
Text("custom button")
}))
}
}.navigationViewStyle(StackNavigationViewStyle())
}
private func btnPressed() {
isLinkActive = true
}
}
struct SecondView: View {
var body: some View {
Color.blue
}
}
You need to create custom back button for destination view as well,and you shouldn’t set navigation title for navigationLink, that’s why you are not able to hide “View1” correctly.
Check below code.
import SwiftUI
struct Test: View {
#State var isLinkActive = false
var body: some View {
NavigationView {
VStack {
NavigationLink("go to the second view", destination: SecondView()
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: Button(action: {
isLinkActive = false
}, label: {
HStack{
Image(systemName: "backward.frame.fill")
Text("View1")
}
})) ,
isActive: $isLinkActive)
}.navigationBarItems(leading: Button(action: {
()
}, label: {
Text("custom button")
}))
}.navigationViewStyle(StackNavigationViewStyle())
}
private func btnPressed() {
isLinkActive = true
}
}
struct SecondView: View {
var body: some View {
Color.blue
}
}
You can try and make navigationBar code as reusable component, because you might need to do this at multiple places.
Output-:
I achieved this by using two modifiers on my main view. Similar to your case, I didn't want a title on the first view, but I wanted the back button on the pushed view to read < Home, not < Back.
.navigationTitle("Home")
.toolbar {
ToolbarItem(placement: .principal) {
Text("")
}
}

NavigationTitle visual glitches - transparent and not changing state from .large to .inline on scroll

The .navigationTitle on some views seem to be having some problems. On some views (and only some of the time), the .navigationTitle will not change from .large to .inline as would be expected. Instead, the title stays in place when scrolling up, and the navigation bar is completely invisible (as outlined in the video below). This is all reproducible every time.
Video of reproducible .navigationTitle bugs
I haven't found any people on stack overflow or the Apple Developer forums who have run into this exact issue. There have some people who have produced similar results as this, but those were all fixed by removing some stylizing code to the .navigationbar, of which I am not making any modifications to it anywhere in my code.
Below are some snippets of my code:
import SwiftUI
struct WelcomeUI: View {
var body: some View {
NavigationView {
VStack {
//NavigationLink(destination: SignupUI(), label: {
//Text("Sign Up")
//}
NavigationLink(destination: LoginUI(), label: {
Text("Log In")
})
}
}
}
}
struct LoginUI: View {
var body: some View {
VStack {
NavigationLink(destination: MainUI(), label: { Text("Log In") })
//Button(action: { ... }
}
.navigationBarHidden(false)
}
}
struct MainUI: View {
#State var selectedTab: Views = .add
var body: some View {
TabView(selection: $selectedTab) {
SpendingView()
.tabItem {
Image(systemName: "bag.circle")
Text("Spending")
}.tag(Views.spending)
Text("Adding View")
.tabItem {
Image(systemName: "plus")
Text("Add")
}.tag(Views.add)
Text("Edit View")
.tabItem {
Image(systemName: "pencil")
Text("Edit")
}.tag(Views.edit)
SettingsView()
.tabItem {
Image(systemName: "gear")
Text("Settings")
}.tag(Views.settings)
}
.navigationBarTitle(Text(selectedTab.rawValue))
.navigationBarBackButtonHidden(true)
}
}
enum Views: String {
case spending = "Spending"
case add = "Add"
case edit = "Edit"
case settings = "Settings"
}
struct SettingsView: View {
var body: some View {
VStack{
ZStack {
Form {
Section(header: Text("Section Header")) {
NavigationLink(destination: WelcomeUI()) {
Text("Setting Option")
}
}
Section {
//Button("Log Out") {
//self.logout()
//}
Text("Log Out")
}
}
Button("say-high", action: {print("Hi")})
}
}
}
}
struct SpendingView: View {
var body: some View {
ScrollView{
Text("SpendingView")
NavigationLink("subSpending", destination: SubSpendingView())
}.padding()
}
}
struct SubSpendingView: View {
var body: some View {
ScrollView{
Text("SubSpendingView")
}.navigationBarTitle("SubSpending")
}
}
It almost seems like a bug in SwiftUI itself just because the fact that bringing down the control centre makes it kind of work, but with no animation (as seen in the video). Also, changing which view is selected first in #State var selectedTab: Views seems to let the view selected to work as expected, but lets the rest of the tabs mess up.
When I build and run the app on my iPad, it behaves as expected with no bugs, it's only when run on my iPhone and the iOS simulator on Mac that it does this, any way to fix this?
For this to work flawlessly the ScrollView needs to be the direct child of the NavigationView. I ran into a similar issue with wanting to dismiss the TabView when I navigating but SwiftUI won't let that happen. Each tab needs to be a NavigationView and you need to dismiss the TabView creatively if that is what you want.
TabView {
NavigationView {
ScrollView {
// your view here
}
}.tabItem {
// tab label
}
// etc
}
Essentially the navigation view needs to be a child (in the brackets) of the tab view and the scrollview needs to be the direct child of the navigation view.
Use navigationBarTitle("Title") and navigationBarBackButtonHidden(true) on the TabView's sub-view, not on itself.
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
}
.navigationBarTitle("Title")
.navigationBarBackButtonHidden(true)
}
}
}

How to replace the current view in SwiftUI?

I am developing an app with SwiftUI.
I have a NavigationView and I have buttons on the navigation bar. I want to replace the current view (which is a result of a TabView selection) with another one.
Basically, when the user clicks "Edit" button, I want to replace the view with another view to make the edition and when the user is done, the previous view is restored by clicking on a "Done" button.
I could just use a variable to dynamically choose which view is displayed on the current tab view, but I feel like this isn't the "right way to do" in SwiftUI. And this way I could not apply any transition visual effect.
Some code samples to explain what I am looking for.
private extension ContentView {
#ViewBuilder
var navigationBarLeadingItems: some View {
if tabSelection == 3 {
Button(action: {
print("Edit pressed")
// Here I want to replace the tabSelection 3 view by another view temporarly and update the navigation bar items
}) {
Text("Edit")
}
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
TabView(selection: $tabSelection) {
ContactPage()
.tabItem {
Text("1")
}
.tag(1)
Text("Chats")
.tabItem() {
Text("2")
}
.tag(2)
SettingsView()
.tabItem {
Text("3")
}
.tag(3)
}.navigationBarItems(leading: navigationBarLeadingItems)
}
}
}
Thank you
EDIT
I have a working version where I simply update a toggle variable in my button action that makes my view display one or another thing, it is working but I cannot apply any animation effect on it, and it doesn't look "right" in SwiftUI, I guess there is something better that I do not know.
If you just want to add animations you can try:
struct ContentView: View {
...
#State var showEditView = false
var body: some View {
NavigationView {
TabView(selection: $tabSelection) {
...
view3
.tabItem {
Text("3")
}
.tag(3)
}
.navigationBarItems(leading: navigationBarLeadingItems)
}
}
}
private extension ContentView {
var view3: some View {
VStack {
if showEditView {
FormView()
.background(Color.red)
.transition(.slide)
} else {
Text("View 3")
.background(Color.blue)
.transition(.slide)
}
}
}
}
struct FormView: View {
var body: some View {
Form {
Text("test")
}
}
}
A possible alternative is to use a ViewRouter: How To Navigate Between Views In SwiftUI By Using An #EnvironmentObject.

Resources