navigationBarHidden broken with SwiftUI Xcode beta? - swift

I'm trying out SwiftUI for an demo project and my navigationBarHidden fails on one computer while succeeding on another. They are both on the Xcode Beta.
It is a very simple transit app with a tab bar and navigation view as its root. The detail view has a custom header with floating buttons for backward navigation as well as a "more" menu. I want to hide the navigation bar to make this as visually appealing as possible, but right now I can't get the nav bar to disappear one one of my machines.
struct Dashboard: View {
var body: some View {
NavigationView {
TabView {
SavedStopsBrowser().tabItem {
Label(Verbiage.shared.saved, systemImage: "star")
}
StopSearcher().tabItem {
Label(Verbiage.shared.search, systemImage: "magnifyingglass")
}
}.accentColor(AppColors.primary)
}
}
}
The detail view of the saved stop is as such:
struct StopViewer: View {
#Environment(\.presentationMode) var presentation
let stop: TrainStop
init(stop: TrainStop) {
self.stop = stop
}
var body: some View {
GeometryReader { geometry in
ScrollView {
VStack(spacing: 0) {
CollapseableStopHeader(stop: stop, geometry: geometry, backButtonAction: {
self.presentation.wrappedValue.dismiss()
}, moreButtonAction: {
print("User did press more button")
})
TransitLineCatalog(lines: stop.associatedTrainLines)
.padding(EdgeInsets(top: 0, leading: 8, bottom: 0, trailing: 10))
}
}
.navigationBarHidden(true)
.ignoresSafeArea(edges: .top)
}
}
}
Any thoughts or suggestions as a way to make this work? Am I doing something horribly wrong?

Related

Button gets overlapped by navigation bar SwiftUI watchOS

I have recently been developing an app for Apple Watch using SwiftUI. I have integrated NavigationView and embedded another View into it. This embedded view has got few buttons, which I can't figure out how to size properly. The top row is overlapped by navigation bar. Is there some workaround how to size it properly? I have tried using Text instead of button and with it it works fine.
This is structure of my main view:
NavigationView {
ScrollView(.vertical) {
NavigationLink(destination: InputView()) {
Text("Click here to get view")
}
}
}
This is structure of InputView:
var body: some View {
NavigationView {
VStack(spacing: 0) {
ForEach(0..<4) { rowIndex in
HStack {
ForEach(0..<3) { columnIndex in
let buttonID = rowIndex*3 + columnIndex
Button("\(buttonID)") {
print(buttonID)
}
}
}
}
}
}
}
Here's an image that shows the functionality

Add Padding to TabView Page Indicator

I have a view that is a TabView with the style PageTabViewStyle(indexDisplayMode: .always which looks great for my use case however the page indicator is bumping right up to the safe area near the bottom of the screen and it looks bad. I'd like to move the page indicator up to some n value. here's a sample of my view to reproducing it. If this view were built on any device without a Home Button, it will ride on top of the home indicator line.
var body: some View {
ZStack {
TabView(selection: $homeVM.selectedPageIndex) {
// Any number of views here.
}
.frame(width: UIScreen.main.bounds.width)
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .always))
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .never))
}.edgesIgnoringSafeArea(.all)
}
I attempted to add the padding to the ZStack, which does work but then my TabView is cut off on the bottom, which means my cells disappear prematurely.
Here's an image for what I'm trying to fix. Notice the page indicator sits on the home bar indicator. I need the indicators pushed up, without pushing up the background ScrollView
Update #1
This view is being presented by a base view that I use to handle my navigation stack. The view is as follows. The important thing to note here is the .ignoresSafeArea() that I have on this view. I did that because it's a containing view for my eventual TabView to be presented from. Interestingly if I remove this modifier the indicators move up to a more manageable position, but then my form becomes clipped at the top and bottom of the device when scrolling, and that's not ideal.
struct BaseLaunchView: View {
#StateObject var baseNavVM = BaseLaunchViewModel()
#State var shouldLogin = false
#State var shouldRegister = false
var body: some View {
VStack {
switch baseNavVM.loggedIn {
case true:
HomeView()
default:
NavigationView {
VStack{
Spacer()
VStack(spacing: 30) {
VStack {
Text("Stello")
.font(Fonts.title)
Text("Life Groups")
.font(Fonts.body)
}
StelloDivider()
Text("Connect with like minded people, to fellowship and find your home.")
.font(Fonts.subheading)
.multilineTextAlignment(.center)
}
Spacer()
NavigationLink(destination: RegisterOptionsView(isLoggingIn: true), isActive: $shouldLogin) {
Button(action: {
shouldLogin.toggle()
}, label: {
Text("Login")
.font(Fonts.button)
}).buttonStyle(StelloFillButtonStyle())
}
NavigationLink(destination: RegisterOptionsView(isLoggingIn: false), isActive: $shouldRegister) {
Button(action: {
shouldRegister.toggle()
}, label: {
Text("Register")
.font(Fonts.button)
}).buttonStyle(StelloHollowButtonStyle())
}
}
}.accentColor(.black)
}
}
.padding()
.environmentObject(baseNavVM)
.ignoresSafeArea()
}
}

How can I change my SwiftUI background without losing the navigation bar UI?

I'm trying to change my View background colour to a specific color, however, whenever I add it using the basic Zstack way, it loses the navigation bar UI. (See pictures)
EDITED CODE
This method is not working for me:
var body: some View {
ZStack{
Color("Background")
.edgesIgnoringSafeArea(.vertical)
VStack {
ScrollView {
ZStack {
VStack {
HStack{
VStack (alignment: .leading) {
Text("")
}
}
}
}
}
Text("")
}
}
}
Current UI with simple ZStack:
Desired UI:
How do I change my background color in SwiftUI without losing the navigation bar UI?
At this period of SwiftUI evolution it is possible only as workaround via UIKit
Here is a demo of possible approach (tested with Xcode 12.1 / iOS 14.1):
var body: some View {
NavigationView {
VStack {
ScrollView {
VStack {
ForEach(0..<50) {
Text("Item \($0)")
}
}
}
Text("Test").navigationTitle("Test")
.background(UINavigationConfiguration { nc in
nc.topViewController?.view.backgroundColor = .yellow
})
}
}
}
Note: the UINavigationConfiguration is taken from next my answer https://stackoverflow.com/a/65404368/12299030

SwiftUI NavigationView trying to pop to missing destination (Monoceros?)

I'm using Xcode 12 with deployment for iOS 14.0.
My home screen has a NavigationView
Within the NavigationView there is a TabView (with 4 tabs)
Within each tab are subviews that have buttons and NavigationLinks
The navigation on the app is functioning correctly (when I click a NavigationLink on one of the subviews, it navigates to the correct view and when I click the back button, it dismisses the view.) However, when I click the back button, the console prints the following error:
Trying to pop to a missing destination at /Library/Caches/com.apple.xbs/Sources/Monoceros/Monoceros-103/Shared/NavigationBridge_PhoneTV.swift:337
Aside from the error log, the app is functioning fine, so I'm planning to just ignore the error for now... but I'm wondering what it means? I don't have anything within my code named "Monoceros". I'm guessing it has something to do with the TabView being a subview of the NavigationView?
EDIT:
Several months later, this issue still persists. Here is reproducible code. Open the ContentView(), on the FirstScreen() click on the NavigationLink, then click the back button. It will print out Monoceros lol
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
TabView {
FirstScreen()
.tabItem {
Text("One")
Image(systemName: "house.fill")
}
Text("Second Screen")
.tabItem {
Text("Two")
Image(systemName: "heart.fill")
}
}
}
}
}
struct FirstScreen: View {
var body: some View {
NavigationLink("Click here", destination: Text("Final Screen"))
// Click the back button on FinalScreen prints:
//Trying to pop to a missing destination at /Library/Caches/com.apple.xbs/Sources/Monoceros_Sim/Monoceros-120/Shared/NavigationBridge_PhoneTV.swift:341
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Unfortunately this is an active issue with a TabView placed inside NavigationView.
The error would not manifest itself if you placed your NavigationView inside the TabView, but this of course would lead to the tabs being displayed from within your Final Screen, which you are probably trying to avoid.
                                      
There is currently no work-around for this, and as of to date we need to wait for Apple to properly implement the corresponding .navigationBarHidden() of TabViews as well.
Problems and unexpected behaviours have been reported when embedding a TabView into a NavigationView, however if you have tested your app thoroughly and found no particular problems, it is safe to say that you can stick to this method.
Alternatively you will have to build a TabView component manually, like below:
import SwiftUI
enum Tab {
case house, heart
}
struct TabView: View {
#Binding var tabIdx: Tab
var body: some View {
HStack {
Group {
Spacer()
Button (action: {
self.tabIdx = .house
}) {
VStack{
Image(systemName: "house.fill")
Text("House")
.font(.system(size: 10))
}
}
.foregroundColor(self.tabIdx == .house ? .blue : .secondary)
Spacer()
Button (action: {
self.tabIdx = .heart
}) {
VStack{
Image(systemName: "heart.fill")
Text("Heart")
.font(.system(size: 10))
}
}
.foregroundColor(self.tabIdx == .heart ? .blue : .secondary)
Spacer()
}
}
.padding(.bottom, 30)
.padding(.top, 10)
.background(Color(red: 0.1, green: 0.1, blue: 0.1))
.font(.system(size: 30))
.frame(height: 80)
}
}
struct FirstScreen: View {
var body: some View {
NavigationLink("Click here", destination: Text("Final Screen"))
.font(.system(size:20))
}
}
struct ContentView: View {
#State var tabIdx: Tab = .house
var body: some View {
NavigationView {
VStack(spacing: 20) {
Spacer()
if tabIdx == .house {
FirstScreen()
} else if tabIdx == .heart {
Text("Second Screen")
}
Spacer(minLength: 0)
TabView(tabIdx: self.$tabIdx)
}
.ignoresSafeArea()
}
}
}
The above bug is well detailed in this blog post, which you could consult for further reference and more examples.

SwiftUI - Navigation bar button not clickable after sheet has been presented

I have just started using SwiftUI a couple of weeks ago and i'm learning. Today I ran into a into an issue.
When I present a sheet with a navigationBarItems-button and then dismiss the ModalView and return to the ContentView I find myself unable to click on the navigationBarItems-button again.
My code is as follows:
struct ContentView: View {
#State var showSheet = false
var body: some View {
NavigationView {
VStack {
Text("Test")
}.sheet(isPresented: self.$showSheet) {
ModalView()
}.navigationBarItems(trailing:
Button(action: {
self.showSheet = true
}) {
Text("SecondView")
}
)
}
}
}
struct ModalView: View {
#Environment(\.presentationMode) var presentation
var body: some View {
VStack {
Button(action: {
self.presentation.wrappedValue.dismiss()
}) {
Text("Dismiss")
}
}
}
}
I think this happens because the presentationMode is not inherited from the presenter view, so the presenter didn't know that the modal is already closed. You can fix this by adding presentationMode to presenter, in this case to ContentView.
struct ContentView: View {
#Environment(\.presentationMode) var presentation
#State var showSheet = false
var body: some View {
NavigationView {
VStack {
Text("Test")
}.sheet(isPresented: self.$showSheet) {
ModalView()
}.navigationBarItems(trailing:
Button(action: {
self.showSheet = true
}) {
Text("SecondView")
}
)
}
}
}
Tested on Xcode 12.5.
Here is the full working
example.
This seems to be a bug in SwiftUI. I am also still seeing this issue with Xcode 11.5 / iOS 13.5.1. The navigationBarMode didn't make a difference.
I filed an issue with Apple:
FB7641003 - Taps on a navigationBarItem Button presenting a sheet sometimes not recognized
You can use the attached example project SwiftUISheet (also available via https://github.com/ralfebert/SwiftUISheet) to reproduce the issue. It just presents a sheet from a navigation bar button.
Run the app and tap repeatedly on the 'plus' button in the nav bar. When the sheet pops up, dismiss it by sliding it down. Only some taps to the button will be handled, often a tap is ignored.
Tested on Xcode 11.4 (11E146) with iOS 13.4 (17E255).
Very hacky but this worked for me:
Button(action: {
self.showSheet = true
}) {
Text("SecondView")
.frame(height: 96, alignment: .trailing)
}
I'm still seeing this issue with Xcode 13 RC and iOS 15. Unfortunately the solutions above didn't work for me. What I ended up doing is adding a small Text view to the toolbar whose content changes depending on the value of the .showingSheet property.
struct ContentView: View {
#State private var showingSheet = false
var body: some View {
NavigationView {
VStack {
Text("Content view")
Text("Swift UI")
}
.sheet(isPresented: $showingSheet) {
Text("This is a sheet")
}
.navigationTitle("Example")
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
// Text view workaround for SwiftUI bug
// Keep toolbar items tappable after dismissing sheet
Text(showingSheet ? " " : "").hidden()
Button(action: {
self.showingSheet = true
}) {
Label("Show Sheet", systemImage: "plus.square")
}
}
}
}
}
}
I realize it's not ideal but it's the first thing that worked for me. My guess is that having the Text view's content change depending on the .showingSheet property forces SwiftUI to fully refresh the toolbar group.
So far, I can still observe the disorder of navi buttons right after dismissing its presented sheet.
FYI, I am using a UINavigationController wrapper instead as workaround. It works well.
Unfortunately, I sure that the more that kind of bugs, the farther away the time of using SwiftUI widely by the ios dev guys. Because those are too basic to ignore.
Very hacky but this worked for me:
I had the same problem. this solution worked for me.
struct ContentView: View {
#State var showSheet = false
var body: some View {
NavigationView {
VStack {
Text("Test")
}.sheet(isPresented: self.$showSheet) {
ModalView()
}.navigationBarItems(trailing:
Button(action: {
self.showSheet = true
}) {
Text("SecondView")
// this is a workaround
.frame(height: 96, alignment: .trailing)
}
)
}
}
}
Only #adamwjohnson5's answer worked for me. I don't like doing it but it's the only solution that works as of Xcode 13.1 and iOS 15.0. Here is my code for anyone interested in seeing iOS 15.0 targeted code:
var body: some View {
NavigationView {
mainContentView
.navigationTitle(viewModel.navigationTitle)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
PlusButton {
viewModel.showAddDialog.toggle()
}
.frame(height: 96, alignment: .trailing) // Workaroud, credit: https://stackoverflow.com/a/62209223/5421557
.confirmationDialog("CatalogView.Add.DialogTitle", isPresented: $viewModel.showAddDialog, titleVisibility: .visible) {
Button("Program") {
viewModel.navigateToAddProgramView.toggle()
}
Button("Exercise") {
viewModel.navigateToAddExerciseView.toggle()
}
}
}
}
.sheet(isPresented: $viewModel.navigateToAddProgramView, onDismiss: nil) {
Text("Add Program View")
}
.sheet(isPresented: $viewModel.navigateToAddExerciseView, onDismiss: nil) {
AddEditExerciseView(viewModel: AddEditExerciseViewModel())
}
}
.navigationViewStyle(.stack)
}