NavigationView with NavigationLinks replacing different part of the screen? - swiftui

I am trying to implement with SwiftUI a main view (for a game), which has an ad banner at the bottom. When the user navigates from main view to the settings view, the same ad banner should stay there and keep showing the ad. But when the user navigates from the main view to the gameplay view, the banner should not be visible.
I am struggling to implement this with NavigationView. Depending on how I position the NavigationView in the view hierarchy, all the NavigationLinks either leave the ad banner in place or hide it. I have tried to use one NavigationView only and also played around with two different NavigationViews, both nested and non-nested, but nothing seems to work properly...
Below a there is a simple code snippet which does not work, but gives you something to work on. Both of the links leave the red "Ad banner" at the bottom. If I move the "Ad banner" code (Spacer and HStack) inside the inner VStack, then both of the links go to a view with no ad.
How to have differently behaving NavigationLinks in a same view, where one replaces the whole screen and the other leaves the ad visible below?
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
NavigationView {
VStack {
NavigationLink(destination: Text("No Ads")) {
Text("Link to a view with no Ads") // How to make this link to hide the Ad below?
}
NavigationLink(destination: Text("Ad visible")) {
Text("Link to a view with same Ad visible") // This link works as expected
} // Try moving the Ad banner right under here to see the other beavior
}
}
Spacer() // This below is the Ad banner
HStack {
Spacer()
Text("Ad is shown here")
.padding()
Spacer()
}
.background(Color.red)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

You can show the Ad Banner depending on which Navigation Link was clicked.
struct ContentView: View {
#State var firstActive : Bool = false
#State var secondActive : Bool = false
var body: some View {
VStack {
NavigationView {
VStack {
NavigationLink(destination: Text("No Ads"), isActive: self.$firstActive) {
Text("Link to a view with no Ads") // How to make this link to hide the Ad below?
}
NavigationLink(destination: Text("Ad visible"), isActive: self.$secondActive) {
Text("Link to a view with same Ad visible") // This link works as expected
} // Try moving the Ad banner right under here to see the other beavior
}
}
if (secondActive || (!secondActive && !firstActive))
{
Spacer() // This below is the Ad banner
HStack {
Spacer()
Text("Ad is shown here")
.padding()
Spacer()
}
.background(Color.red)
}
}
}
}
Using two States which are used as Binding in NavigationLink will keep track which NavigationLink is active. Then only show the Ad Banner when non is active or only the second one.

Related

In SwiftUI, how do I draw a view above a bottom toolbar?

I have a view with a bottom toolbar. When some user action is performed, I would like to display a drawer coming from the bottom that will cover this toolbar. I wasn't able to find a way to do this - the toolbar always remains on top. Here's an example of something I've tried:
struct ContentView: View {
var body: some View {
NavigationView {
ZStack {
VStack {
Spacer()
Text("Bottom Text")
}
.ignoresSafeArea(.all, edges: .bottom)
Text("Hello, world!")
.toolbar {
ToolbarItemGroup(placement: .bottomBar) {
Text("Bottom Bar")
}
}
}
}
}
}
The "Bottom Text" label gets hidden behind the bottom toolbar.
The one thing I've tried so far is to remove the .toolbar modifier when the drawer is shown (by creating a custom view modifier that returns the .toolbar modifier conditionally). This works but seems like a hack, and also has some UI artifacts as the removal of the toolbar can shift some of the other view.
Any ideas how this can be solved?
Thanks!
To show content on top of the bottom toolbar, add your views as an overlay on the NavigationView itself:
var body: some View {
NavigationView {
Text("Hello, world!")
.toolbar {
ToolbarItemGroup(placement: .bottomBar) {
Button("Bottom Bar", action: {})
}
}
}
.overlay(
VStack {
Spacer()
Text("Bottom Text")
}
)
}
Original answer:
Remove the ignoresSafeArea. The bottom safe area includes the toolbar, so by ignoring it you’re telling Swiftui to put the content behind the toolbar.

Disable or ignore taps on TabView in swiftui

I have a pretty usual app with a TabView. However, when a particular process is happening in one of the content views, I would like to prevent the user from switching tabs until that process is complete.
If I use the disabled property on the TabView itself (using a #State binding to drive it), then the entire content view seems disabled - taps don't appear to be getting through to buttons on the main view.
Example:
struct FooView: View {
var body: some View {
TabView {
View1().tabItem(...)
View2().tabItem(...)
}
.disabled(someStateVal)
}
}
Obviously, I want the View1 to still allow the user to, you know, do things. When someStateVal is true, the entire View1 doesn't respond.
Is there a way to prevent changing tabs based on someStateVal?
Thanks!
I could not find a way to individually disable a tabItem, so here is
an example idea until someone comes up with more principled solution.
The trick is to cover the tab bar with a clear rectangle to capture the taps.
struct ContentView: View {
#State var isBusy = false
var body: some View {
ZStack {
TabView {
TestView(isBusy: $isBusy)
.tabItem {Image(systemName: "globe")}
Text("textview 2")
.tabItem {Image(systemName: "info.circle")}
Text("textview 3")
.tabItem {Image(systemName: "gearshape")}
}
VStack {
Spacer()
if isBusy {
Rectangle()
.fill(Color.white.opacity(0.001))
.frame(width: .infinity, height: 50)
}
}
}
}
}
struct TestView: View {
#Binding var isBusy: Bool
var body: some View {
VStack {
Text("TestView")
Button(action: {
isBusy.toggle()
}) {
Text("Busy \(String(isBusy))").frame(width: 170, height: 70)
}
}
}
}
I use another trick. Just hide the tab image.
struct FooView: View {
var body: some View {
TabView {
View1().tabItem{Image(systemName: someStateVal ? "": "globe")}
View2().tabItem{Image(systemName: someStateVal ? "": "gearshape")}
}
}
}

Navigation Link Returning Back Two Levels on Exit

I am having trouble with a return from a navigation view within a tabbed view. My project has a Settings tab where the user may select via navigation link "View Entries". And from there another navigation link to "Add New Entry". Returning from Add New Entry should bring you to View Entries but instead is return another level to the Setting Menu.
I am seeing a warning on the console stating "trying to pop to a missing destination at /Library/Caches/com.apple...". Using the tabbed view sample code at SwiftUI NavigationView trying to pop to missing destination (Monoceros?) I no longer get the "pop-to-missing-destination" warning but I still have the same problem with the navigation return.
The sample code below is ready to run and test in Xcode 12.
In the sample code below, tap settings and select the navigation view "View Entries". This would be a screen where entries are displayed in a list. Tapping the plus button is where new entries could be added. The textfield on the "Add New Entry" screen doesn't do anything. Clicking the Save or Back buttons should return you to "View Entries" screen but instead returns you to the Setting Menu. The Save button uses presentationMode.wrappedValue.dismiss to dismiss the view.
The fact that two different version of the tab view logic didn't have any impact on my navigation view return logic leads me to believe that I just have some kind on plain old bug in my navigation view logic but I sure don't see one. The sample code below is using the standard tab view logic.
struct ContentView: View {
#State private var selection = 0
var body: some View {
NavigationView {
TabView (selection: $selection) {
HomeView()
.tabItem {
Label("Home", systemImage: "house")
}.tag(1)
AView()
.tabItem {
Label("A", systemImage: "a.circle")
}.tag(2)
BView()
.tabItem {
Label("B", systemImage: "b.circle")
}.tag(3)
SettingsView()
.tabItem {
Label("Settings", systemImage: "gearshape")
}.tag(4)
}
}
}
}
struct HomeView: View {
var body: some View {
Text("Home Screen")
}
}
struct AView: View {
var body: some View {
Text("A Screen")
}
}
struct BView: View {
var body: some View {
Text("B Screen")
}
}
struct SettingsView: View {
var body: some View {
VStack (alignment: .leading) {
List {
Text("Settings")
.font(.title)
.fontWeight(.bold)
.padding(.leading, 15)
NavigationLink(destination: SetAView()) {Text("View Entries")}
}
}
.font(.body)
}
}
struct SetAView: View {
var body: some View {
List {
Text("View Entries")
.padding(.vertical, 10)
Text("Normally entires would be displayed here")
Text("Should return here upon adding new entry")
.padding(.vertical, 10)
Text("Click the + button to add new entry")
}
.navigationBarItems(trailing: NavigationLink (destination: AddTestView()) {
Image(systemName: "plus")
.resizable()
.foregroundColor(Color(.systemBlue))
.frame(width: 18, height: 18)
} // body
)
}
}
struct AddTestView: View {
#Environment(\.presentationMode) var presentationMode
#State private var catSelect: String = ""
var body: some View {
NavigationView {
VStack {
Form {
Section {
TextField("Enter Entry Name", text: $catSelect)
.padding(.horizontal, 20)
.keyboardType(.default)
}
}
}
.navigationBarTitle(Text("Add new Entry"), displayMode: .inline)
.navigationViewStyle(StackNavigationViewStyle())
.navigationBarItems(trailing: Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Text ("Save")
})
}
}
}
After considerable analysis I discovered in my actual code that I had two copies of NavigationView––nothing wrong with the TabView code. Removing the one NavigationView not in contentView then caused several functions from working so rebuilt them from scratch. Now have everything working with TabView and NaigationView including the back buttons.

SwiftUI Navigation bar items disappear on iOS 14

Discovered in my app that navigation bar items in some views disappear when orientation of the device changes. This seems to occur only in a view that is opened using NavigationLink, on main view navigation bar items work as expected. It appears that something has changed between iOS 13.7 and iOS 14.2 related to this. Also, it does not seem to matter whether using leading or trailing items, both disappear.
Example snippet where this occurs:
struct ContentView: View {
var detailView: some View {
Text("This is detail view")
.navigationBarTitle("Detail view title", displayMode: .inline)
.navigationBarItems(trailing: Button(action: {}, label: {
Image(systemName: "pencil.circle.fill")
}))
}
var body: some View {
NavigationView {
NavigationLink(
destination: detailView,
label: {
Text("Open detail view")
})
.navigationBarTitle("Main view")
}.navigationViewStyle(StackNavigationViewStyle())
}
}
The issue occurs only when running on a real device. (iPhone 11 in my case) On simulator everything works as expected.
Anyone else seen similar issues? Workarounds/fixes?
.navigationBarTitle and .navigationBarItems are being deprecated. I think that the best "fix" is to switch to .toolbar
It's a weird issue but I guess there is a hack to make it work.
SwiftUI doesn't call body property when rotations happen. So you can add #State UIDeviceOrientation property to your view and update it every time orientation changes. One tricky thing is to use that property somewhere in the body of the view since SwiftUI smart enough to ignore #State that is not used in the body.
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(
destination: DetailsView(),
label: {
Text("Open detail view")
}).navigationBarTitle("Main view")
}.navigationViewStyle(StackNavigationViewStyle())
}
}
struct DetailsView: View {
#State var orientation: UIDeviceOrientation = UIDevice.current.orientation
var body: some View {
Text("This is detail view")
.navigationBarTitle("Detail view title")
.navigationBarItems(trailing: button)
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
orientation = UIDevice.current.orientation
}.background(Text("\(orientation.rawValue)"))
}
var button: some View {
Button(action: { print("123") }, label: {
Image(systemName: "pencil.circle.fill")
}).id("123")
}
}
In my experience when I change a Button in the toolbar from disabled to enabled, they disappear. But if I scroll to the bottom of the View, they re-appear. If I am already at the end of the View when the button is enabled, it acts normally, until I then scroll away from the bottom, the button again disappears.
Try scrolling to the bottom of your view, if you use landscape.

Hidden Navbar still pushes down view

I have a:
contentView()
SignUpView()
SignInView()
The contentView calls the SignInView()
struct ContentView: View {
var body: some View {
NavigationView {
SignInView()
}
}
}
In my SignUpView() I have:
var body: some View {
VStack(alignment: .leading) {
NavigationLink(destination: SignInView()) {
Text("Sign in")
.fontWeight(.semibold)
.foregroundColor(Color("startColor"))
}
}.navigationBarHidden(true)
In my SigbInView I have:
var body: some View {
VStack(alignment: .leading) {
NavigationLink(destination: SignUpView()) {
Text("Sign up")
.fontWeight(.semibold)
.foregroundColor(Color("startColor"))
}.navigationBarHidden(true)
Im using .navigationBarHidden(true) to hide the bar, but the < back still appears in the top left hand corner to take you back to the previous screen, Iv also tried adding the navbar text = "" and setting the property to .inline
Im trying to only use these navigationLinks on the SignInView and SignUpViews to navigate, i don't want the bar to appear or push the view down.
So it looks like another property can be set to true to hide the back button:
.navigationBarBackButtonHidden(true)
This worked for me.