SwiftUI show/hide title issues with NavigationBar - ios

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

Related

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 NavigationView + TabView doesn't show navbar item

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

Change background color of View inside TabView having NavigationView and ScrollView in SwiftUI

I want to build a TabView with 4 tabs having collection views in it. Below is my code of one tab named 'Gallery'.
var body: some View {
NavigationView {
ScrollView {
GridStack(rows: 3, columns: 2) { row, column, totalColumn in
CardView(card: self.cards[(row * totalColumn) + column])
}.padding().background(Color.red)
}
.navigationBarTitle("Gallery")
}
}
When I give background color for ScrollView, scrolling is not working for NavigationView largeTitle. How can I achieve this, I want to give red color for full view's background? What if I need to achieve this same backgorund color for all tabs?
Here is possible approach (scroll view is not broken in such case)
NavigationView {
GeometryReader { gp in
ScrollView {
ZStack(alignment: .top) {
Rectangle().fill(Color.red) // << background
// ... your content here, internal alignment might be needed
}.frame(minHeight: gp.size.height)
}
.navigationBarTitle("Gallery")
}
}

Status bar disappears after unlocking with SwiftUI

I have an app with Status Bar initially hidden: YES and
NavigationView {
}
.statusBar(hidden: true)
When the app starts, the bar is not visible but its space is not occupied (so it looks like additional padding from top). But when I lock/unlock the phone this padding disappears thus moving the whole app closer to the top.
Any suggestions what's causing it?
Problem
Just tested it with the following view and it really jumps up after you lock/unlock the screen. Try to take a screenshot and it jumps up as well (found this accidentally when I was taking screenshots for the answer).
struct ContentView: View {
var body: some View {
NavigationView {
Color.green
.navigationBarTitle("No status bar")
}
.statusBar(hidden: true)
}
}
Workaround
Just add the line marked in the code below.
struct ContentView: View {
var body: some View {
NavigationView {
Color.green
.navigationBarTitle("No status bar")
}
.edgesIgnoringSafeArea(.all) // <-------
.statusBar(hidden: true)
}
}
IMHO it should work without this line, but it doesn't apparently. This problem is here even if I set (Info.plist):
UIViewControllerBasedStatusBarAppearance to YES
UIStatusBarHidden to YES
Or if I hide status bar directly on the ContentView (SceneDelegate):
let contentView = ContentView().statusBar(hidden: true)

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