Custom NavigationView gets messed up when importing the presentationMode Environment variable - swiftui

I'm using a custom navigation bar view & setup, where I hide it in the main view and show it in the child. All works fine, until I added this line in the child:
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
I needed it in the child so we can pop back to the main view. Now when I open the child then click on back (not even using the env variable) then open the child view again the navigation bar is not ignoring the safe areas as it was before. Here's a gif of the issue:
See the new grey space in the child view?
This is the code for this:
import SwiftUI
#main
struct NavBarApp: App {
var body: some Scene {
WindowGroup {
NavigationView {
MainView()
}
}
}
}
struct MainView: View {
init() {
let appearance = UINavigationBarAppearance()
appearance.configureWithTransparentBackground()
appearance.backgroundColor = .clear
UINavigationBar.appearance().standardAppearance = appearance
UINavigationBar.appearance().compactAppearance = appearance
UINavigationBar.appearance().scrollEdgeAppearance = appearance
UINavigationBar.appearance().isTranslucent = false
UINavigationBar.appearance().prefersLargeTitles = false
}
var body: some View {
VStack {
Spacer()
NavigationLink("Go to child view", destination: ChildView())
Spacer()
}
.edgesIgnoringSafeArea(.all)
.navigationBarHidden(true)
.tabItem {
Text("Home")
}
}
}
struct ChildView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
VStack {
Rectangle()
.frame(maxWidth: .infinity, maxHeight: 66)
.foregroundColor(Color.black)
.edgesIgnoringSafeArea(.all)
Spacer()
Text("Chlid View")
.font(.largeTitle)
.padding()
Spacer()
}
.edgesIgnoringSafeArea(.all)
.navigationBarTitle("View A", displayMode: .inline)
.navigationBarItems(trailing: AnyView(Button(action: {
}) {
Text("Add")
}))
}
}
What I noticed:
Removing the presentationMode fixes the issue.
Not adding a button on the navigation bar in the child view fixes the issue.
Not customising the NavigationBar's appearance in MainView also fixes the issue.
Any ideas on what's going on and how to fix this?

Okay I narrowed it down to this line in the appearance setup:
UINavigationBar.appearance().isTranslucent = false
Removing it solved the issue. Removing it hasn't affected the look of the custom navigation bar so I'll go with this solution for now.

Related

SwiftUI UINavigationBar does not ignore the top safe area. How to get rid of empty space on the top?

Code I used to create an example of this navigation bar:
struct ContentView: View {
init() {
let defaultAppearance = UINavigationBarAppearance()
defaultAppearance.configureWithOpaqueBackground()
defaultAppearance.backgroundColor = .red
UINavigationBar.appearance().standardAppearance = defaultAppearance
UINavigationBar.appearance().setBackgroundImage(UIImage(), for: .default)
}
var body: some View {
ParentView()
}
}
struct ParentView: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: DetailView()) {
Text("Tap here")
}
}
.navigationBarTitle("", displayMode: .inline)
.navigationBarHidden(true)
.edgesIgnoringSafeArea(.all)
}
}
}
struct DetailView: View {
var body: some View {
VStack {
Text("Detail View")
}
.edgesIgnoringSafeArea(.all)
}
}
It doesn't matter where I'm putting .edgesIgnoringSafeArea(.all) it doesn't work. Is there another way to tell Navigation Bar to ignore the safe area?
If it's not the safe area problem, then I need to make the whole navigation bar to have the same height of UINavigationBarContentView displayed in UI Hierarchy:

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.

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.

SwiftUI How to change the NavigationBarTitle and add a button from a child form

I have a mainview which has 2 tabs. I placed the navigationView at the master page so I can access to its functionality in the child views. However, the title is never successfully assigned to the master view.
The second objective is adding a button on the navigation button from the child view. However, the form doesn't render it.
Here is the code. Could you please help me with changing the title from the child view and adding a button to NavigationView from the child view?
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView{
MainView()
.navigationBarTitle(Text("App Title"), displayMode: .inline)
}
}
}
struct MainView: View {
var body: some View {
TabView(){
DashboardView()
.navigationBarTitle(Text("Dashboard"), displayMode: .inline)
.tabItem{
//Image(systemName: "list.dash")
Image(systemName: "chart.pie")
Text("Dashboard")
}.tag(0)
AssignmentView()
.navigationBarTitle(Text("Assignments"), displayMode: .inline)
.tabItem{
Image(systemName: "briefcase")
Text("Assignments")
}.tag(1)
}
}
}
struct DashboardView: View {
var body: some View {
Text("Dashboard View")
.navigationBarTitle(Text("Dashboard View Title"), displayMode: .inline)
}
}
struct AssignmentView: View {
var body: some View {
Text("Assignment View")
.navigationBarTitle(Text("Assignments View Title"), displayMode: .inline)
.navigationBarItems(trailing: Button(action: {
print("Dashboard button click")
}) {
Text("Submit")
})
}
}
For proper navigation features work there should be only one NavigationView in view stack. I assume you just need to remove first NavigationView, because your MainView is TabView container
struct ContentView: View {
var body: some View {
MainView()
}
}
At least for me, such variant gives native look&feel.
I removed the NavView from the MainView and added NavViews to the subviews with the help of new struct NavigationTab.
struct NavigationTab<Title, Content>: View where Title: StringProtocol, Content: View {
var title: Title
var content: () -> Content
var body: some View {
NavigationView {
content()
.navigationBarTitle(Text(title), displayMode: .inline)
}
}
}
The tab items now look like
NavigationTab(title: "Dashboard" ) {
DashboardView()
.navigationBarItems(leading: Button(action: {
self.isDrawerOpen.toggle()
}) {
Image(systemName: "sidebar.left")
})
}
.tabItem{
Image(systemName: "chart.pie")
Text("Dashboard")
}.tag(1)
I solved an initial problem of adding button on the NavView by replicating DrawerOpen Button in multiple tabs and assign the same "isDrawerOpen" variable.
But I still have a use case in the process where I need to add a button to the NavView.trailing for Submitting. Is there a way to add a button in a subview (for example DashboardView calls another view)?