How to remove the default Navigation Bar space in SwiftUI NavigationView
Asked Answered
G

26

220

I am new to SwiftUI (like most people) and trying to figure out how to remove some whitespace above a List that I embedded in a NavigationView.

In this image, you can see that there is some white space above the List.

Current Version

What I want to accomplish is this:

Ideal Version

I've tried using:

.navigationBarHidden(true)

but this did not make any noticeable changes.

I'm currently setting up my navigiationView like this:

NavigationView {
    FileBrowserView(jsonFromCall: URLRetrieve(URLtoFetch: applicationDelegate.apiURL))
        .navigationBarHidden(true)
}

where FileBrowserView is a view with a List and FileCells defined like this:

List {
   Section(header: Text("Root")) {
       FileCell(name: "Test", fileType: "JPG",fileDesc: "Test number 1")
       FileCell(name: "Test 2", fileType: "txt",fileDesc: "Test number 2")
       FileCell(name: "test3", fileType: "fasta", fileDesc: "")
    }
}

I do want to note that the ultimate goal here is that you will be able to click on these cells to navigate deeper into a file tree and thus should display a Back button on the bar on deeper navigation, but I do not want anything at the top as such during my initial view.

Grenier answered 16/8, 2019 at 1:1 Comment(1)
My question is the opposite of yours, my default is the effect you want, but I want top padding, is there any way to help me? #74243327Kanishakanji
S
265

For some reason, SwiftUI requires that you also set .navigationBarTitle for .navigationBarHidden to work properly.

NavigationView {
    FileBrowserView(jsonFromCall: URLRetrieve(URLtoFetch: applicationDelegate.apiURL))
        .navigationBarTitle("")
        .navigationBarHidden(true)
}

Update

As @Peacemoon pointed out in the comments, the navigation bar remains hidden as you navigate deeper in the navigation stack, regardless of whether or not you set navigationBarHidden to false in subsequent views. As I said in the comments, this is either a result of poor implementation on Apple's part or just dreadful documentation (who knows, maybe there is a "correct" way to accomplish this).

Whatever the case, I came up with a workaround that seems to produce the original poster's desired results. I'm hesitant to recommend it because it seems unnecessarily hacky, but without any straightforward way of hiding and unhiding the navigation bar, this is the best I could do.

This example uses three views - View1 has a hidden navigation bar, and View2 and View3 both have visible navigation bars with titles.

struct View1: View {
    @State var isNavigationBarHidden: Bool = true

    var body: some View {
        NavigationView {
            ZStack {
                Color.red
                NavigationLink("View 2", destination: View2(isNavigationBarHidden: self.$isNavigationBarHidden))
            }
            .navigationBarTitle("Hidden Title")
            .navigationBarHidden(self.isNavigationBarHidden)
            .onAppear {
                self.isNavigationBarHidden = true
            }
        }
    }
}

struct View2: View {
    @Binding var isNavigationBarHidden: Bool

    var body: some View {
        ZStack {
            Color.green
            NavigationLink("View 3", destination: View3())
        }
        .navigationBarTitle("Visible Title 1")
        .onAppear {
            self.isNavigationBarHidden = false
        }
    }
}

struct View3: View {
    var body: some View {
        Color.blue
            .navigationBarTitle("Visible Title 2")
    }
}

Setting navigationBarHidden to false on views deeper in the navigation stack doesn't seem to properly override the preference of the view that originally set navigationBarHidden to true, so the only workaround I could come up with was using a binding to change the preference of the original view when a new view is pushed onto the navigation stack.

Like I said, this is a hacky solution, but without an official solution from Apple, this is the best that I've been able to come up with.

Sabah answered 16/8, 2019 at 2:38 Comment(18)
This fixed my issue! That is very strange that you have to have a title before you can hide the navigation bar...Grenier
@Grenier It must be a bug since it's still in beta.Hanoi
The bug is still there outside of beta :/Windflower
but with this solution, you don't have the default back button anymore, how can you go back?Atop
@Atop I didn't notice that before. All in all, it feels like the implementation from Apple is pretty sloppy here. You shouldn't have to set the title just to hide the bar to begin with, and setting navigationBarHidden to false on the next view should unhide the navigation bar, but it doesn't. I ultimately got fed up with just how poorly documented SwiftUI was and went back to UIKit, and the fact that at least 20 people came here just to learn how to hide the navigation bar speaks pretty poorly for Apple's implementation and/or documentation. Sorry I don't have a better answer for you.Sabah
@Atop I edited my answer with a potential workaround. It's not exactly pretty, but it seems to work fairly well. Let me know what you think!Sabah
totally agree with you that the documentation is really unhelpful. Thank you for your investigation, looks quite hacky :)Atop
Today I saw one more issue with navigationBarHidden. The workaround solution is not working for me, if I am using a TabView inside NavigationView.Downtrodden
@SambitPrakash I’ve never really nested a TabView inside a NavigationView before, and Apple doesn’t seem to nest them that way in their apps as far as I can tell. It’s never been completely clear to me if nesting a TabView inside a NavigationView is intended to be done at all, and I know that SwiftUI has had some weird bugs that pop up when you nest them that way. TabViews have always felt like a higher level form of navigation than NavigationViews to me. If you instead nest the NavigationView inside the TabView, I believe my workaround should still work.Sabah
the bug still there (XCode 11.3)Creaky
it is still happening to me, just lost a bunch of hours trying to know what's going on, this is still the recommended workaround?Canadianism
Is it possible to navigate between views without the NavigationView at all? The proposed solution breaks if you try to swipe back between screens without leaving the screen (the navbar disappears and you have no way to navigate).Mon
@Mon It’s disappointing that this answer is still getting attention and upvotes. I wrote it as a temporary solution to what should have been a temporary bug. I have not tested it recently, but obviously there are plenty of problems with it. Several people have also asked if you can navigate between views without using a NavigationView. The answer is yes, but you’d essentially have to write your own NavigationView from scratch. You can’t just magically navigate between views. Something has to manage those views and provide transitions between them which is why we have NavigationView.Sabah
@Sabah thanks for your input. I've done some more research, and so far it seems you could either use a router library like this or use swiftui sheet(), which may be enough depending on your UX.Mon
This is the only way I've found to get this working, and it's sad that we have to resort to this sort of hackery to achieve something so simple. Kudos to @Sabah for figuring it out.Essay
The solution mentioned below from @femkeo, also helped me out greatly.Sour
To understand why this is necessary, remember that SwiftUI is based on UIKit! there in the UIViewController viewDidLoad() you set the NavigationViewController to hidden: NavigationController?.setNavigationBarHidden(true, animated: false) Then in viewWillDisappear you set it to false so you have the bar again. So we have this workaround until Apple gives us something better...Ladysmith
I need to keep the navigation bar but remove the space that is saved for the largeTitle so I can bring my content up closer to the bar. I don't need the title nor an inline title, but I do need the buttons. Is there a way to do that?Formaldehyde
B
42

View Modifiers made it easy:

//ViewModifiers.swift

struct HiddenNavigationBar: ViewModifier {
    func body(content: Content) -> some View {
        content
        .navigationBarTitle("", displayMode: .inline)
        .navigationBarHidden(true)
    }
}

extension View {
    func hiddenNavigationBarStyle() -> some View {
        modifier( HiddenNavigationBar() )
    }
}

Example: enter image description here

import SwiftUI

struct MyView: View {
    var body: some View {
        NavigationView {
            VStack {
                Spacer()
                HStack {  
                    Spacer()
                    Text("Hello World!")
                    Spacer()
                }
                Spacer()
            }
            .padding()
            .background(Color.green)
            //remove the default Navigation Bar space:
            .hiddenNavigationBarStyle()
        }
    }
}
Brisket answered 2/3, 2020 at 15:37 Comment(2)
Doesn't fix the issue for a pushed view controller.Essay
It seems key here that the modifier is not added to the NavigationView but the view just inside. This made all the difference getting it to work. Thanks! :-)Carrell
A
36

iOS 14+

There is a dedicated modifier to make the navigation bar take less space:

.navigationBarTitleDisplayMode(.inline)

EDIT

In some cases it may still be needed to add .navigationBarHidden(true)

Accountable answered 2/11, 2020 at 20:38 Comment(2)
This just makes the navigation bar smaller. There is still need to remove it altogether.He
This was a helpful solution to minimize the space it takes up, thank you!Snapdragon
A
32

The purpose of a NavigationView is to add the navigation bar on top of your view. In iOS, there are 2 kinds of navigation bars: large and standard.

enter image description here

If you want no navigation bar:

FileBrowserView(jsonFromCall: URLRetrieve(URLtoFetch: applicationDelegate.apiURL))

If you want a large navigation bar (generally used for your top-level views):

NavigationView {
    FileBrowserView(jsonFromCall: URLRetrieve(URLtoFetch: applicationDelegate.apiURL))
    .navigationBarTitle(Text("Title"))
}

If you want a standard (inline) navigation bar (generally used for sub-level views):

NavigationView {
    FileBrowserView(jsonFromCall: URLRetrieve(URLtoFetch: applicationDelegate.apiURL))
    .navigationBarTitle(Text("Title"), displayMode: .inline)
}

Hope this answer will help you.

More information: Apple Documentation

Anaclinal answered 16/8, 2019 at 2:38 Comment(5)
There are reasons why you might want to hide the navigation bar while also maintaining the functionality of a NavigationView. The purpose of a NavigationView is not solely to display a navigation bar.Sabah
I want the NavigiationView for the functionality of navigating in the stack and having the ability to go back from views easily, I don't need a navigiationBar on the initial view.Grenier
Is there a way to navigate through views without navigationView?Lancaster
Basically. No. Not yet on swiftui at leastBil
This answer isn't helpful as having a NavigationView is impact in the original question as it's needed to navigate to another view.Carrell
M
17

If you set the title as inline for the View you want remove the space on, this doesn't need to be done on a view with a NavigationView, but the one navigated too.

.navigationBarTitle("", displayMode: .inline)

Starting issue solution 1 then simply change the Navigation bars appearance

init() {
    UINavigationBar.appearance().setBackgroundImage(UIImage(), for: .default)
    UINavigationBar.appearance().shadowImage = UIImage()
}

on the view that holds the initial NavigationView. final solution

If you want to change the Appearance from screen to screen change the appearance in the appropriate views

Marche answered 14/8, 2020 at 12:29 Comment(1)
This solution is usefulInartistic
J
11

For me, I was applying the .navigationBarTitle to the NavigationView and not to List was the culprit. This works for me on Xcode 11.2.1:

struct ContentView: View {
    var body: some View {
        NavigationView {
            List {
                NavigationLink(destination: DetailView()) {
                    Text("I'm a cell")
                }
            }.navigationBarTitle("Title", displayMode: .inline)
        }
    }
}

Navigation bar and list with no gap at the top

Jemappes answered 12/2, 2020 at 14:25 Comment(3)
@AhmedSahib The question was "How to remove the default Navigation Bar space in SwiftUI NavigiationView" and my code accomplishes that.Jemappes
Excellent advice. For my solution I had to apply two modifiers to the inner list to get rid of the spacing: .navigationBarTitle("", displayMode: .automatic) .navigationBarHidden(true) Then on the outer NavigationView I had to apply: .navigationBarTitle("TITLE", displayMode: .inline)Stereotype
Thanks bro. I also had the same problem.Decomposition
L
10

This is a bug present in SwiftUI (still as of Xcode 11.2.1). I wrote a ViewModifier to fix this, based on code from the existing answers:

public struct NavigationBarHider: ViewModifier {
    @State var isHidden: Bool = false

    public func body(content: Content) -> some View {
        content
            .navigationBarTitle("")
            .navigationBarHidden(isHidden)
            .onAppear { self.isHidden = true }
    }
}

extension View {
    public func hideNavigationBar() -> some View {
        modifier(NavigationBarHider())
    }
}
Laruelarum answered 24/11, 2019 at 15:50 Comment(2)
With this, the "swift to back" gesture is no longer workingSabaean
I also needed to add .onDisappear { self.isHidden = false } to get the navbar to appear in subviews. Also the swipe back gesture seems to work fine.Protoplasm
G
10

I also tried all the solutions mentioned on this page and only found @graycampbell solution the one to be working well, with well-working animations. So I tried to create a value I can just use throughout the app that I can access anywhere by the example of hackingwithswift.com

I created an ObservableObject class

class NavBarPreferences: ObservableObject {
    @Published var navBarIsHidden = true
}

And pass it on to the initial view in the SceneDelegate like so

var navBarPreferences = NavBarPreferences()
window.rootViewController = UIHostingController(rootView: ContentView().environmentObject(navBarPreferences))

Then in the ContentView we can keep track of this Observable object like so and create a link to SomeView:

struct ContentView: View {
    //This variable listens to the ObservableObject class
    @EnvironmentObject var navBarPrefs: NavBarPreferences

    var body: some View {
        NavigationView {
                NavigationLink (
                destination: SomeView()) {
                    VStack{
                        Text("Hello first screen")
                            .multilineTextAlignment(.center)
                            .accentColor(.black)
                    }
                }
                .navigationBarTitle(Text(""),displayMode: .inline)
                .navigationBarHidden(navBarPrefs.navBarIsHidden)
                .onAppear{
                    self.navBarPrefs.navBarIsHidden = true
            }
        }
    }
}

And then when accessing the second view (SomeView), we hide it again like this:

struct SomeView: View {
    @EnvironmentObject var navBarPrefs: NavBarPreferences

    var body: some View {
        Text("Hello second screen")
        .onAppear {
            self.navBarPrefs.navBarIsHidden = false
        }
    } 
}

To keep previews working add the NavBarPreferences to the preview like so:

struct SomeView_Previews: PreviewProvider {
    static var previews: some View {
        SomeView().environmentObject(NavBarPreferences())
    }
}
Gibbosity answered 28/5, 2020 at 20:52 Comment(1)
using @EnvironmentObject is much better to pass data throughout the app rather than @State, so I prefer you answer moreBanta
E
10

Put on your NextView the following code

        .navigationBarBackButtonHidden(true)
        .navigationBarHidden(true)

But while pushing to the NextView via NavigationLink you have to put also the modifier like this :

        NavigationLink(
            destination: NextView()
                .navigationBarTitle("")
                .navigationBarHidden(true)
        ) {
            Text("NEXT VIEW")
        }
                    
Eada answered 1/4, 2021 at 16:7 Comment(0)
H
10

You don't need to set the title, you can simply use .stack

NavigationView {
     VStack {
         Color.cyan
      }
     .navigationBarHidden(true)
}
.navigationViewStyle(.stack)  // Here
Heterosexual answered 5/2, 2022 at 5:28 Comment(2)
Last answer best answer, wow. ZStack can be used instead of VStack as well.Sharma
Format that code... 🙄Condition
Z
7

You could extend native View protocol like this:

extension View {
    func hideNavigationBar() -> some View {
        self
            .navigationBarTitle("", displayMode: .inline)
            .navigationBarHidden(true)
    }
}

Then just call e.g.:

ZStack {
    *YOUR CONTENT*
}
.hideNavigationBar()
Zoller answered 22/1, 2020 at 16:3 Comment(0)
S
6

For me it was because I was pushing my NavigationView from an existing. In effect having one inside the other. If you are coming from a NavigationView you do not need to create one inside the next as you already inside a NavigatonView.

Solomonsolon answered 19/2, 2020 at 8:48 Comment(0)
N
4

My solution for this problem was the same as suggested by @Genki and @Frankenstein.

I applied two modifiers to the inner list (NOT the NavigationView) to get rid of the spacing:

.navigationBarTitle("", displayMode: .automatic)
.navigationBarHidden(true) 

On the outer NavigationView, then applied .navigationBarTitle("TITLE") to set the title.

Northwesterly answered 31/8, 2020 at 1:34 Comment(1)
It doesn't do anything.Loosejointed
D
4

I tried setting up .navigationBarTitle("", displayMode: .inline) .navigationBarHidden(true) But it wasn't working. The issue was I was setting it to

NavigationView{...}.navigationBarTitle("", displayMode: .inline)
        .navigationBarHidden(true)

But to get rid of the NagigationBar it should be set to inner view of it

NavigationView{
InnerView{}.navigationBarTitle("", displayMode: .inline)
        .navigationBarHidden(true)
}

Hope this helps To see in action, You can look into this open source App(WIP) https://github.com/deepaksingh4/KidsBookApp

Deeprooted answered 30/3, 2021 at 18:40 Comment(0)
G
4

I try to add .navigationBarHidden(true) at the end of curly brackets of my Vstack like this

NavigationView { Vstack(){"some Code"}.navigationBarHidden(true)}

and the navigation bar disappear enter image description here but if i add .navigationBarHidden(true) at the end of curly brackets of navigation bar like this

    NavigationView { Vstack(){"some Code"}}.navigationBarHidden(true)

the navigation bar doesn't disappear enter image description here

Giotto answered 12/5, 2021 at 23:26 Comment(0)
N
4

Same problem, I finally solved. For the navigation to completely disappear, you need to add these modifier to the NavigationView AND ALL NavigationsLinks inside it:

.navigationBarHidden(true)
.navigationBarTitleDisplayMode(.inline)

If you don't do it also with the NavigationLinks won't work.

Nomarch answered 6/12, 2021 at 10:17 Comment(0)
T
3

Similar to the answer by @graycampbell but a little simpler:

struct YourView: View {

    @State private var isNavigationBarHidden = true

    var body: some View {
        NavigationView {
            VStack {
                Text("This is the master view")
                NavigationLink("Details", destination: Text("These are the details"))
            }
                .navigationBarHidden(isNavigationBarHidden)
                .navigationBarTitle("Master")
                .onAppear {
                    self.isNavigationBarHidden = true
                }
                .onDisappear {
                    self.isNavigationBarHidden = false
                }
        }
    }
}

Setting the title is necessary since it is shown next to the back button in the views you navigate to.

Trachoma answered 27/12, 2019 at 16:57 Comment(0)
C
2

I have had a similar problem when working on an app where a TabView should be displayed once the user is logged in.

As @graycampbell suggested in his comment, a TabView should not be embedded in a NavigationView, or else the "blank space" will appear, even when using .navigationBarHidden(true)

I used a ZStack to hide the NavigationView. Note that for this simple example, I use @State and @Binding to manage the UI visibility, but you may want to use something more complex such as an environment object.

struct ContentView: View {

    @State var isHidden = false

    var body: some View {
        
        ZStack {
            if isHidden {
                DetailView(isHidden: self.$isHidden)
            } else {
                NavigationView {
                    Button("Log in"){
                        self.isHidden.toggle()
                    }
                    .navigationBarTitle("Login Page")
                }
            }
        }
    }
}

When we press the Log In button, the initial page disappears, and the DetailView is loaded. The Login Page reappears when we toggle the Log Out button

struct DetailView: View {
    
    @Binding var isHidden: Bool
    
    var body: some View {
        TabView{
            NavigationView {
                Button("Log out"){
                    self.isHidden.toggle()
                }
                .navigationBarTitle("Home")
            }
            .tabItem {
                Image(systemName: "star")
                Text("One")
            }
        }
    }
}
Catenoid answered 24/7, 2020 at 4:18 Comment(2)
Strangely I had this exact problem. I had a setup/login flow that made sense to be in a NavigationView but once it was completed I wanted to show a TabView. I used a combination of this answer and an EnvironmentObject signaling when to switch to the TabView. I didn't want to pass state/binding down through all the multiple navigation so EnvironmentObject made more sense.Omission
I could not understand why do we need ZStack there?Syringomyelia
I
1

I struggled on this for a while, but what finally worked for me is...

ZStack {
    ...
}
.edgesIgnoringSafeArea(.all) //or .edgesIgnoringSafeArea(.top)
.navigationBarBackButtonHidden(true)
.navigationBarHidden(true)
Inscription answered 10/3, 2021 at 3:51 Comment(0)
S
1

I have to navigate screen1 to screen2. If I use this for NavigationView like above answer Navigation bar will be hidden but its space still exist ( amount of space with height) in Screen 1.

Finally I have own solution that use this code in any view inside NavigationView and don't care about navigationBarTitle. Just like this:

Screen1:

NavigationView {
    SomeView {
        NavigationLink {
        // go to screen2
        }
    }.navigationBarHidden(true)
}

Screen2:

NavigationView {
// some Views
}.navigationBarHidden(true)
Saum answered 13/12, 2021 at 9:13 Comment(0)
A
1

I had the same issue and found the following code to work best.

   .navigationTitle("")
   .navigationBarBackButtonHidden(true)
Alexiaalexin answered 5/4, 2022 at 21:2 Comment(0)
L
0

This is by far the most simplest and stable approach I've found. You can hide both navigation title and back button by hiding the whole toolbar. You can show also choose to show it in any view you wish to. You can hide it by using .toolbar(.hidden) and make it visible by using the .toolbar(.visible) modifier.

iOS 16+

struct ContentView: View {
    var body: some View {
        NavigationStack {
            List {
                ForEach(0..<10) { i in
                    NavigationLink {
                        Text("Detail for Row \(i)")
                    } label: {
                        Text("Row \(i)")
                    }
                }
            }
            
            .toolbar(.hidden)
        }
    }
}

If you targeting below iOS 16, you can replace the NavigationStack with NavigationView.

Lapse answered 30/9, 2022 at 2:36 Comment(0)
T
0

Try putting the attributes (navigation title, toolbar, etc) outside of the Navigation View. Like so:

NavigationView {
}
.navigationTitle("Detail News")
.toolbarColorScheme(.dark, for: .navigationBar)
.toolbarBackground(Color.gray, for: .navigationBar)
.toolbarBackground(.visible, for: .navigationBar)
.accentColor(.white)
Thrips answered 10/11, 2022 at 15:52 Comment(0)
M
-1

Really loved the idea given by @Vatsal Manot To create a modifier for this.
Removing isHidden property from his answer, as I don't find it useful as modifier name itself suggests that hide navigation bar.

// Hide navigation bar.
public struct NavigationBarHider: ViewModifier {

    public func body(content: Content) -> some View {
        content
            .navigationBarTitle("")
            .navigationBarHidden(true)
    }
}

extension View {
    public func hideNavigationBar() -> some View {
        modifier(NavigationBarHider())
    }
}
Mellisamellisent answered 2/3, 2020 at 7:17 Comment(0)
C
-1

I know I'm a bit late here, but I just fixed this problem using the top answer here: How to get rid of space in nested NavigationView with SwiftUI

In case the content of that page changes, I'll explain the answer below.

Only use a NavigationView wrapper at the very top level of any view that needs navigation, no matter how far the nested children go down. They will all already have NavigationView properties and you can call NavigationLink at any time within the sub-views. I had a lot of extra NavigationView wrappers around sub-views, deleting them removed the extra white space while retaining the functionality of all navigation links.

Corroboree answered 18/4, 2022 at 5:5 Comment(0)
G
-9

Try putting the NavigationView inside a GeometryReader.

GeometryReader {
    NavigationView {
        Text("Hello World!")
    }
}

I’ve experienced weird behavior when the NavigationView was the root view.

Greggrega answered 17/8, 2019 at 3:4 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.