SwiftUI disappear back button with navigationLink - ios

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.

Related

SwiftUI NavigationBar not extending to top of iPhone screen with ScrollView

In my SwiftUI View I have a ScrollView filled with text for attribution to show what frameworks were used in my app.
Once this text scrolls past the navigation bar it should be hidden past that point to the very top of the iPhone screen BUT in my case the text reappears because my navigation bar seems to only be a certain height (see image below) and does not extend to the top of the screen and prevent text from reappearing.
The attribution view below is inside of a AboutView which is also inside of a SettingsView that contains a NavigationView.
Any idea why this is happening? Image is attached..
TEXT RE-APPEARING PAST NAVIGATION BAR
PARENT VIEW
struct Settings: View {
#Environment(\.presentationMode) var presentation
#State private var presentAbout: Bool = false
var body: some View {
NavigationView {
VStack {
Text("About").onTapGesture { presentAbout.toggle() }
}
.navigationBarTitle("Settings", displayMode: .inline)
.navigationBarBackButtonHidden(true)
.toolbar(content: {
Button(action: {
self.presentation.wrappedValue.dismiss()
}, label: { Text("Cancel") })
})
.background ( NavigationLink("", destination: AboutMenu(), isActive: $presentAbout ))
}
}
}
SUB VIEW 1
struct AboutMenu: View {
#Environment(\.presentationMode) var presentation
#State private var presentAttribution: Bool = false
var body: some View {
VStack {
Text("Attribution").onTapGesture { presentAttribution.toggle() }
}
.navigationBarTitle("About", displayMode: .inline)
.navigationBarBackButtonHidden(true)
.toolbar(content: {
ToolbarItem(placement: .navigationBarLeading) {
Button(action: { self.presentation.wrappedValue.dismiss() }, label: {
Image(systemName: "chevron.backward")
}
})
.background ( NavigationLink("", destination: Attribution(), isActive: $presentAttribution))
}
}
SUB VIEW 2 Where problem exists.
struct Attribution: View {
#Environment(\.presentationMode) var presentation
var body: some View {
VStack {
ScrollView {
Text(attribution) // <- THIS TEXT shows behind NavigationBar past the navigation bar.
}
}
.navigationBarTitle("Attribution", displayMode: .inline)
.navigationBarBackButtonHidden(true)
.toolbar(content: {
ToolbarItem(placement: .navigationBarLeading) {
Button(action: { self.presentation.wrappedValue.dismiss() }, label: {
Image(systemName: "chevron.backward")
}
})
}
}
This can happen when ScrollView is not root view of the NavigationView, so it does not recognise scroll view presence.
It can be fixed by explicit clipping, like
VStack {
ScrollView {
Text(introText)
}
.clipped() // << here !!

add an exit button to .navigationBarItems

I'm a beginner at swiftui. I need to add an exit button to .navigationBarItems. How can I add this button in the parent NavigationView to show this button on all children's views?
// a simple example on my question
struct FirstView: View {
var body: some View {
NavigationView {
ZStack{
TabView{
SubExampleViewOne()
.tabItem {
Image(systemName: "house.fill")
Text("Home")
}
SubExampleViewTwo()
.tabItem {
Image(systemName: "bookmark.circle.fill")
Text("Bookmark")
}
}
}
//here I have added a toolbar and it is perfectly visible in tabitem
//this is what I am trying to achieve, the visibility of the button on all pages
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
ButtonExitView()
}
}
}
}
}
something strange - if I add NavigationLink in this way, Image and Text("Home") are visible twice
and the ToolbarItem is no longer on the new page
struct SubExampleViewOne: View {
var body: some View {
Text("This is hime page!")
.padding()
NavigationLink(destination: SubExampleViewThree()){
Text("Navigation link")
}
}
}
struct SubExampleViewTwo: View {
var body: some View {
Text("Hello, world!")
.padding()
}
}
struct SubExampleViewThree: View {
var body: some View {
Text("This is Navigation link")
.padding()
}
}
struct ButtonExitView: View {
var body: some View {
Button(action: {}, label: {Image(systemName: "arrowshape.turn.up.right.circle")})
}
}
after learning about TabView, I thought that there should be a similar solution for the top of the page
You have to add the button to each child view separately.
And you should use .toolbar and .toolBarItem because .navigationBarItems is deprecated.

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 StackNavigationViewStyle issue when rotating iPhone

I want to implement a Settings view which can be opened taping on a gear icon button in the navigation tool bar.
This button opens a SwiftUI sheet with on Ok button to validate settings and close the settings window.
It works well if you use it without rotating the iPhone.
But if you rotate the phone when the settings window is opened, the Ok button does not work anymore and the window stays on screen (even if you rotate back the phone).
In the console, an error appears when I rotate the phone. Here is the message:
[Presentation] Attempt to present <…> on <…> (from <…>) which is already presenting
This issue seems to be linked to StackNavigationViewStyle() modifier I use to not have 2 columns on landscape mode.
If I remove the following line, the bug disappears but the layout is no more the one I want.
.navigationViewStyle(StackNavigationViewStyle())
Here is a sample code I wrote to reproduce the problem:
import SwiftUI
struct ContentView: View {
// Size class
#Environment(\.verticalSizeClass) var sizeClassV
#State private var showGearView: Bool = false
var gearButton: some View {
HStack {
Button(action: {
self.showGearView.toggle()
}) {
Image(systemName: "gear")
.imageScale(.large)
.accessibility(label: Text("Settings"))
}
.sheet(isPresented: self.$showGearView, onDismiss: {
}, content: {
gearView()
})
}
}
var body: some View {
return NavigationView {
// if sizeClassV == .regular {
VStack {
Text("Click on Gear and rotate your iPhone: here is the bug when clicking on Ok: the sheet does not collapse!")
.multilineTextAlignment(.center)
.padding(.all)
}
.padding(.all)
.navigationTitle("Bug")
.navigationBarTitleDisplayMode(.inline)
.toolbar(content: { gearButton })
}
// The bug only happens when adding the StackNavigationViewStyle below
.navigationViewStyle(StackNavigationViewStyle())
}
}
struct gearView: View {
#Environment(\.presentationMode) var presentationMode
var OKButton: some View {
HStack {
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Text("OK")
}
}
}
var body: some View {
return NavigationView {
VStack {
Form {
Section(header: Text("Settings")) {
Text("No Settings")
}
}
}
.navigationTitle(Text("Settings"))
.toolbar(content: {
ToolbarItem(placement: .primaryAction) {
OKButton
}
})
}
}
}

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

Resources