Swiftui NavigationView + TabView doesn't show navbar item - ios

I have four Views inside a TabView and each of them contains NavigationView with title. However, when the view first shows up, the navigation view does not show as designed.
Even though I have the navigation bar item, the view would always be a blank child view. It is only when I click to another page and then coming back to the navigation view that the view would show normally. What could be the problems?
Attached is the screenshot of the preview page. Thanks in advance.
struct MainContentView: View {
#State private var navSelection = 0
var body: some View {
VStack(alignment: .center){
TabView(selection:$navSelection){
NavigationView{
HomeView()
.navigationBarItems(leading: Text("Title").font(.system(size:24,weight: .heavy)), trailing: Image(systemName: "bell.fill"))
.navigationViewStyle(StackNavigationViewStyle())
}.tag(0)
NavigationView{
ExploreView()
}.tag(1)
Text("Post").tag(2)
Text("Market").tag(3)
Text("Account").tag(4)
}.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.disabled(true)
MainTabBarView(navSelection: self.$navSelection)
}
}
}
The preview sample

Just a small mistake, apply the style to the navigation view as follows:
NavigationView{
}
.navigationViewStyle(.stack)
What went wrong is some how SwiftUI found a detail view somewhere in the hierarchy and pushed it because normally inside NavigationView there are two views defined but you only defined one. I think .tabViewStyle is what caused it.
Also to set the nav item title we use .navigationTitle("Home")

Related

SwiftUI NavigationView content only visible in iPad sidebar

My app is working as expected across all iPhone models, but when running on iPad I notice that the my application content, which is wrapped within a NavigationView, only displays in the iPad's sidebar, and only after tapping the 'Back` toolbar button.
var body: some View {
NavigationView{
ZStack{
...
}
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
VStack {
Text("Add Light to Your Journey")
.font(Font.custom("EduTASBeginner-Regular", size: 24))
...
}
}
I found a similar question on SO that suggested adding the attribute .navigationViewStyle(.stack), but this did not change the way the app is displayed on iPad:
Note the solution on this similar post also did not resolve the issue.
As noted in the comments on the linked post, the .navigationViewStyle(StackNavigationViewStyle()) must be applied directly to the NavigationView, and not a view contained therein as with .navigationTitle

SwiftUI how to force a divider for navigation bar in NavigationView

My app has simple navigation logic using navigation view. I use the inline style navigation bar:
mainView
.navigationBarTitleDisplayMode(.inline)
I notice that the navigation bar's divider is missing for the root view. And it appears when I scroll up the content a bit.
This first screenshot shows the initial state (without nav bar divider):
This second screenshot shows the state when I scroll up the content a bit, and it shows nav bar divider:
Is it possible to always show the divider without scrolling?
I think you can work around by adding a manual Divider and ScrollView under VStack so that the divider will appear beneath the navigation bar
//
// testUI.swift
// DDStore (iOS)
//
// Created by belal medhat on 19/02/2022.
//
import SwiftUI
struct navTitleBar: View {
var body: some View {
NavigationView {
// main navigationView
VStack(){
// vstack to add the divider and under it the scrollview
Divider()
ScrollView(){
Text("Hello, World!")
}.navigationBarTitleDisplayMode(.inline).navigationTitle("Title")
}
}
}
}
struct testUI_Previews: PreviewProvider {
static var previews: some View {
navTitleBar()
}
}

SwiftUI TextEditor View content is hidden behind Navigation Bar

I'm working on a SwiftUI View with a NavigationBar. The view is very simple, it's a full-page TextEditor:
struct NotesEditingScreen: View {
#State var text: String
var body: some View {
TextEditor(text: $text)
.padding(.horizontal)
.navigationBarTitle("Editing")
}
}
The issue I'm seeing, is that when landing on this screen (via a NavigationLink) the top of the TextEditor is covered up by Navigation Bar:
My desired behavior is that the TextEditor content appears beneath the Navigation Bar, like it appears after you manually scroll to the top to reveal the text.
Is there a solution/workaround to this issue? I was hoping for either some offset, a setting on NavigationBar, or some programmatic scroll behavior that could be done onAppear. Any suggestions welcome.
I think it's an unexpected behavior.
You can try this:
GeometryReader { geo in
ScrollView {
TextEditor(text: $text)
.frame(height: geo.size.height)
}
}

SwiftUI show/hide title issues with NavigationBar

I have the following code construct which gives me a lot of trouble:
//Main View
struct ContentView: View {
var body: some View {
NavigationView{
ZStack(alignment: .center){
CarouselBuilder()
ProfileInvoke().navigationBarTitle("").navigationBarHidden(true)
}
}
}
}
//Carousel filled with Cards from a DB
...code irrelevant for my problem
//Profile Invoke -> Invokes a slide out menu called Menu that has NavigationLinks in it
struct Menu: View {
var body: some View {
ZStack{
VStack(alignment: .center){
MenuButton(buttonText: "Settings", buttonCallView: AnyView(SettingsView() ))
MenuButton(buttonText: "My Favourites", buttonCallView: AnyView(MyFavouritesView()))
MenuButton(buttonText: "Sign Out", buttonCallView: AnyView(SignOutView()))
}.frame(width: UIScreen.main.bounds.width/1.2,alignment: .top)
}
}
}
//MenuButtons are basic NavigationLinks linking to certain Views given as argument when calling them
I now do wrap the ZStack in the Main View in a NavigationView which I need to to in order for the NavigationLinks to work. I also have to do this on this "top" level as I need the new View that will be invoked by the links in the slide out menu to take the entire screen and not only the width the slide out view is being displayed.
My issue is now that I certainly do not want the navigation bar to take up space in the main view. For this I set the hidden attribute for it to true. This tho, carries through the entire app and also disables the navigation view in the subviews linked to by the buttons in the menu. Which gives me no way of going back.
My question would be:
1) Is there a more elegant way of doing all of this?
2) How can I re-invoke the navigation bar in sub views? (Setting the hidden navigation bar attribute on them back to false did not work.
Below is a possible approach to hide navigation bar in root view and show in child subviews. The only needed modifications is in root view.
Tested with Xcode 11.4 / iOS 13.4
Here is a root only, child sub-views are regular and do not require special code for this case. See important notes inline.
struct RootNavigationView: View {
#State private var hideBar = true // << track hide state, and default
var body: some View {
NavigationView {
VStack {
Text("I'm ROOT")
Divider()
NavigationLink("Goto Child", destination: NextChildView(index: 1))
.simultaneousGesture(TapGesture().onEnded {
self.hideBar = false // << show, here to be smooth !!
})
}
.navigationBarHidden(hideBar)
// .navigationBarTitle("Back to Root") // << optional
.onAppear {
self.hideBar = true // << hide on back
}
}
}
}

SwiftUI: How can I restrict the tappable area of a view when presenting a modal(actually not modal) view over a main view?

I am developing an app based on a Tabview with three TabItems. Each TabItem is a List and I would be able to show a kind of modal view over those Lists. The problem becomes when I can not call a Sheet as modal view because Sheets are almost full windowed. I need some kind of bottom modal view, so I create a View that I present over a List with higher ZIndex. It seems to work until you click in the tabbar and select another TabItem having deployed the "modal" view. The error is:
[TableView] Warning once only: UITableView was told to layout its
visible cells and other contents without being in the view hierarchy
(the table view or one of its superviews has not been added to a
window). This may cause bugs by forcing views inside the table view to
load and perform layout without accurate information (e.g. table view
bounds, trait collection, layout margins, safe area insets, etc), and
will also cause unnecessary performance overhead due to extra layout
passes.
So, I would like as solution to restrict the tappable area to the "modal" view area. ¿Is there a way to achieve this?
Probably you have some condition state depending on which you present your "modal-like" view, so depending on the same condition you can disable below TabView, like below
TabView {
// ... tabs content here
}.disabled(showingModal)
Update: Here is a demo of approach that I meant (tested with Xcode 11.3+)
struct TestTabViewModal: View {
#State private var selectedTab = 0
#State private var modalShown = false
var body: some View {
ZStack {
TabView(selection: $selectedTab) {
VStack {
Button("Show Modal") { self.modalShown = true }
.padding(.top, 40)
Spacer()
}
.tabItem {
Image(systemName: "1.circle")
}.tag(0)
Text("2").tabItem {
Image(systemName: "1.circle")
}.tag(1)
}.disabled(modalShown)
if modalShown {
RoundedRectangle(cornerRadius: 10)
.fill(Color.yellow)
.frame(width: 320, height: 240)
.overlay(Button("CloseMe") { self.modalShown = false })
}
}
}
}

Resources