如何删除 SwiftUI NavigationView 中的默认导航栏 space

How to remove the default Navigation Bar space in SwiftUI NavigationView

我是 SwiftUI 的新手(像大多数人一样)并试图弄清楚如何删除我嵌入的 List 上面的一些 whitespaceNavigationView.

在此图像中,您可以看到 List 上方有一些白色 space。

我要完成的是:

我试过使用:

.navigationBarHidden(true)

但这并没有做出任何明显的改变。

我目前正在这样设置我的 navigiationView:

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

其中 FileBrowserView 是具有 ListFileCell 的视图,定义如下:

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

我确实想指出,这里的最终目标是您将能够单击这些单元格以更深入地导航到文件树,因此应该在更深入的导航栏上显示一个后退按钮,但我这样做在我最初的看法中,不希望任何东西出现在顶部。

NavigationView 的目的是在您的视图顶部添加导航栏。在iOS中,有2种导航条:大的和标准的。

如果你不想要导航栏:

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

如果你想要一个大的导航栏(通常用于你的顶级视图):

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

如果你想要一个标准(内联)导航栏(通常用于子级视图):

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

希望这个回答对您有所帮助。

更多信息:Apple Documentation

出于某种原因,SwiftUI 要求您还为 .navigationBarHidden 设置 .navigationBarTitle 才能正常工作。

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

更新

正如@Peacemoon 在评论中指出的那样,无论您是否在后续视图中将 navigationBarHidden 设置为 false,当您在导航堆栈中更深入地导航时,导航栏仍然隐藏。正如我在评论中所说,这要么是 Apple 实施不力的结果,要么只是糟糕的文档(谁知道呢,也许有 "correct" 方法可以做到这一点)。

无论如何,我想出了一个解决方法,似乎可以产生原始海报所期望的结果。我对是否推荐它犹豫不决,因为它看起来不必要地 hacky,但没有任何直接隐藏和取消隐藏导航栏的方法,这是我能做的最好的。

此示例使用三个视图 - View1 有一个隐藏的导航栏,View2View3 都有带标题的可见导航栏。

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

将导航堆栈中较深的视图的 navigationBarHidden 设置为 false 似乎无法正确覆盖最初将 navigationBarHidden 设置为 true 的视图的首选项,所以我能想到的唯一解决方法是在将新视图推送到导航堆栈时使用绑定来更改原始视图的首选项。

就像我说的,这是一个 hacky 解决方案,但没有 Apple 的官方解决方案,这是我能想到的最好的解决方案。

尝试将 NavigationView 放在 GeometryReader 中。

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

NavigationView 是根视图时,我遇到了奇怪的行为。

这是 SwiftUI 中存在的错误(仍然 Xcode 11.2.1)。我写了一个 ViewModifier 来解决这个问题,基于现有答案的代码:

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

与@graycampbell 的回答类似,但更简单:

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

设置标题是必要的,因为它显示在您导航到的视图中的后退按钮旁边。

您可以像这样扩展本机 View 协议:

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

然后只需调用例如:

ZStack {
    *YOUR CONTENT*
}
.hideNavigationBar()

对我来说,我将 .navigationBarTitle 应用于 NavigationView 而不是 List 是罪魁祸首。这适用于 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)
        }
    }
}

对我来说,这是因为我正在从现有的 NavigationView 中推送 NavigationView。实际上有一个在另一个里面。如果您来自 NavigationView,则不需要在 NavigatonView 中创建一个 NavigatonView。

非常喜欢 @Vatsal Manot 提出的想法为此创建一个修饰符。
从他的回答中删除 isHidden 属性,因为我发现它没有用,因为修饰符名称本身建议隐藏导航栏。

// 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())
    }
}

视图修改器 让它变得简单:

//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() )
    }
}

示例:

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

我也尝试了此页面上提到的所有解决方案,只发现@graycampbell 解决方案运行良好,动画效果良好。所以我试图创建一个我可以在整个应用程序中使用的值,我可以通过 hackingwithswift.com

的示例在任何地方访问

我创建了一个 ObservableObject class

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

然后将它传递给 SceneDelegate 中的初始视图,就像这样

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

然后在 ContentView 中我们可以像这样跟踪这个 Observable 对象并创建一个 link 到 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
            }
        }
    }
}

然后在访问第二个视图(SomeView)的时候,我们再次隐藏它,这样:

struct SomeView: View {
    @EnvironmentObject var navBarPrefs: NavBarPreferences

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

要保持​​预览正常工作,请像这样将 NavBarPreferences 添加到预览中:

struct SomeView_Previews: PreviewProvider {
    static var previews: some View {
        SomeView().environmentObject(NavBarPreferences())
    }
}

我在处理用户登录后应显示 TabView 的应用程序时遇到了类似的问题。

正如@graycampbell 在他的评论中建议的那样,不应将 TabView 嵌入到 NavigationView 中,否则即使使用 .navigationBarHidden(true)

也会出现“空白 space”

我使用了 ZStack 来隐藏 NavigationView。请注意,对于这个简单的示例,我使用 @State@Binding 来管理 UI 可见性,但您可能想要使用更复杂的东西,例如环境对象。

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

当我们按下登录按钮时,初始页面消失,并加载 DetailView。当我们切换注销按钮时,登录页面重新出现

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

如果您将要删除 space 的视图的标题设置为内联,则不需要在具有 NavigationView 的视图上完成此操作,但也可以在导航视图上完成。

.navigationBarTitle("", displayMode: .inline)

然后只需更改导航栏外观

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

在包含初始 NavigationView 的视图上。

如果您想在屏幕之间更改外观,请更改相应视图中的外观

我对这个问题的解决方案与@Genki 和@Frankenstein 所建议的相同。

我对内部列表(不是 NavigationView)应用了两个修饰符来消除间距:

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

在外部 NavigationView 上,然后应用 .navigationBarTitle("TITLE") 设置标题。

斯威夫特用户界面 2

有专门的修饰符可以让导航栏占用更少space:

.navigationBarTitleDisplayMode(.inline)

编辑

在某些情况下可能仍需要添加 .navigationBarHidden(true)

我为此苦苦挣扎了一段时间,但最终对我有用的是...

ZStack {
    ...
}
.edgesIgnoringSafeArea(.all) //or .edgesIgnoringSafeArea(.top)
.navigationBarBackButtonHidden(true)
.navigationBarHidden(true)

我试过设置 .navigationBarTitle("", displayMode: .inline) .navigationBarHidden(true) 但它没有用。问题是我将其设置为

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

但是要摆脱 NagigationBar,应该将其设置为内部视图

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

希望对您有所帮助 要查看实际效果,您可以查看这个开源应用程序 (WIP) https://github.com/deepaksingh4/KidsBookApp

在您的 NextView 上输入以下代码

        .navigationBarBackButtonHidden(true)
        .navigationBarHidden(true)

但是在通过 NavigationLink 推送到 NextView 时,您还必须像这样放置修饰符:

        NavigationLink(
            destination: NextView()
                .navigationBarTitle("")
                .navigationBarHidden(true)
        ) {
            Text("NEXT VIEW")
        }
                    

我尝试像这样在我的 Vstack 的大括号末尾添加 .navigationBarHidden(true)

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

导航栏消失但是如果我像这样在导航栏的大括号末尾添加.navigationBarHidden(true)

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

导航栏没有消失

同样的问题,我终于解决了。为了使导航完全消失,您需要将这些修饰符添加到其中的 NavigationView AND ALL NavigationsLinks

.navigationBarHidden(true)
.navigationBarTitleDisplayMode(.inline)

如果您不这样做,NavigationLinks 也将不起作用。

我必须将屏幕 1 导航到屏幕 2。如果我像上面的答案一样将它用于 NavigationView,导航栏将被隐藏,但它的 space 仍然存在(space 的数量和高度)在屏幕 1.

最后,我有了自己的解决方案,可以在 NavigationView 内的任何视图中使用此代码,而不关心 navigationBarTitle。就像这样:

屏幕 1:

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

屏幕 2:

NavigationView {
// some Views
}.navigationBarHidden(true)

⚠️标题不需要设置

NavigationView {
        
            VStack {
                Color.cyan
            }   
        
            .navigationBarHidden(true)
        
        }
    
        .navigationViewStyle(.stack)//⬅️

我遇到了同样的问题,发现以下代码效果最好。

   .navigationTitle("")
   .navigationBarBackButtonHidden(true)

我知道我来晚了一点,但我刚刚使用此处的最佳答案解决了这个问题:

如果该页面的内容发生变化,我将在下面解释答案。

只在任何需要导航的视图的最顶层使用 NavigationView 包装器,无论嵌套的子视图向下延伸多远。它们都已经具有 NavigationView 属性,您可以在 sub-views 内随时调用 NavigationLink。我在 sub-views 周围有很多额外的 NavigationView 包装器,删除它们会移除多余的白色 space,同时保留所有导航链接的功能。