Jak usunąć domyślną przestrzeń paska nawigacji w SwiftUI NavigiationView

94

Jestem nowy w SwiftUI (jak większość ludzi) i próbuję dowiedzieć się, jak usunąć niektóre białe znaki nad listą, którą osadziłem w NavigationView

Na tym obrazku widać, że nad listą znajduje się spacja

Obecna wersja

To, co chcę osiągnąć, to to

Idealna wersja

Próbowałem użyć

.navigationBarHidden(true)

ale to nie spowodowało żadnych zauważalnych zmian.

Obecnie konfiguruję mój NavigiationView w ten sposób

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

gdzie FileBrowserView to widok z listą i komórkami zdefiniowanymi w ten sposób

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

Chcę zauważyć, że ostatecznym celem jest tutaj to, że będziesz w stanie kliknąć te komórki, aby przejść głębiej do drzewa plików, a zatem powinien wyświetlić przycisk Wstecz na pasku podczas głębszej nawigacji, ale nie chcę niczego w top jako taki podczas mojego pierwszego widoku.

Vapidant
źródło

Odpowiedzi:

146

Z jakiegoś powodu, SwiftUI wymaga również ustawić .navigationBarTitlena .navigationBarHiddendo pracy prawidłowo.

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

Aktualizacja

Jak @Peacemoon zauważył w komentarzach, pozostałości pasku nawigacyjnym ukryte, jak poruszać się głębiej w stosie nawigacji, niezależnie od tego, czy zestaw navigationBarHiddendofalse w kolejnych widokach . Jak powiedziałem w komentarzach, jest to albo skutek złej implementacji po stronie Apple, albo po prostu okropnej dokumentacji (kto wie, może istnieje „właściwy” sposób na osiągnięcie tego).

W każdym razie wymyśliłem obejście, które wydaje się przynosić pożądane rezultaty na oryginalnym plakacie. Waham się, czy go polecić, ponieważ wydaje się to niepotrzebnie hakerskie, ale bez prostego sposobu ukrywania i odkrywania paska nawigacyjnego jest to najlepsze, co mogłem zrobić.

W tym przykładzie zastosowano trzy widoki - View1ma ukryty pasek nawigacji View2i View3oba mają widoczne paski nawigacji z tytułami.

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

Ustawianie navigationBarHiddensię falsepoglądami głębiej w stosie nawigacji nie wydaje się właściwie zastąpić preferencje widoku, który pierwotnie ustawiony navigationBarHiddenna true, więc jedyną Obejście mogłem wymyślić używał wiążąca zmienić preferencje oryginalnego widoku, gdy nowy widok jest przenoszony na stos nawigacji.

Jak powiedziałem, jest to hackerskie rozwiązanie, ale bez oficjalnego rozwiązania od Apple jest to najlepsze, jakie udało mi się wymyślić.

graycampbell
źródło
5
To rozwiązało mój problem! To bardzo dziwne, że musisz mieć tytuł, zanim będziesz mógł ukryć pasek nawigacji ...
Vapidant
5
Błąd nadal występuje poza wersją beta: /
Daniel Ryan
1
@Peacemoon Nie zauważyłem tego wcześniej. Podsumowując, wydaje się, że implementacja od Apple jest tutaj dość niechlujna. Nie powinno się ustawić tytuł po prostu ukryć pasek na początek, a ustawienie navigationBarHiddendo falsenastępnego widoku powinien odkryć pasek nawigacji, ale tak nie jest. Ostatecznie miałem dość tego, jak słabo udokumentowany był SwiftUI i wróciłem do UIKit, a fakt, że co najmniej 20 osób przybyło tutaj tylko po to, aby dowiedzieć się, jak ukryć pasek nawigacji, mówi dość słabo o implementacji i / lub dokumentacji Apple. Przepraszam, nie mam dla ciebie lepszej odpowiedzi.
graycampbell
2
@SambitPrakash Nigdy wcześniej tak naprawdę nie zagnieżdżałem TabView w NavigationView, a Apple nie wydaje się umieszczać ich w ten sposób w swoich aplikacjach, o ile wiem. Nigdy nie było dla mnie całkowicie jasne, czy zagnieżdżenie TabView wewnątrz NavigationView ma w ogóle zostać wykonane i wiem, że SwiftUI ma kilka dziwnych błędów, które pojawiają się, gdy zagnieżdżasz je w ten sposób. TabViews zawsze wydawało mi się formą nawigacji wyższego poziomu niż NavigationViews. Jeśli zamiast tego zagnieździsz NavigationView wewnątrz TabView, uważam, że moje obejście powinno nadal działać.
graycampbell
2
@kar To rozczarowujące, że ta odpowiedź wciąż przyciąga uwagę i pozytywne opinie. Napisałem to jako tymczasowe rozwiązanie tego, co powinno być tymczasowym błędem. Nie testowałem tego ostatnio, ale oczywiście jest z nim mnóstwo problemów. Kilka osób zapytało również, czy możesz nawigować między widokami bez korzystania z NavigationView. Odpowiedź brzmi: tak, ale zasadniczo musiałbyś napisać własny NavigationView od podstaw. Nie możesz po prostu magicznie nawigować między widokami. Coś musi zarządzać tymi widokami i zapewniać przejścia między nimi, dlatego mamy NavigationView.
graycampbell
18

Celem a NavigationViewjest dodanie paska nawigacyjnego u góry widoku. W iOS dostępne są 2 rodzaje pasków nawigacyjnych: duże i standardowe.

wprowadź opis obrazu tutaj

Jeśli nie chcesz paska nawigacji:

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

Jeśli chcesz mieć duży pasek nawigacyjny (zwykle używany w widokach najwyższego poziomu):

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

Jeśli chcesz mieć standardowy (wbudowany) pasek nawigacji (zwykle używany w widokach podpoziomów):

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

Mam nadzieję, że ta odpowiedź ci pomoże.

Więcej informacji: Dokumentacja Apple

Przepływ_
źródło
29
Istnieją powody, dla których możesz chcieć ukryć pasek nawigacji, jednocześnie zachowując funkcjonalność pliku NavigationView. Celem a NavigationViewnie jest wyłącznie wyświetlanie paska nawigacji.
graycampbell
6
Chcę, aby NavigiationView zapewniał funkcjonalność nawigacji w stosie i możliwość łatwego cofania się z widoków, nie potrzebuję paska nawigacyjnego w widoku początkowym.
Vapidant
2
Czy istnieje sposób na nawigowanie po widokach bez nawigacji NavigationView?
user1445685
Gruntownie. Nie. Jeszcze nie na swiftui, przynajmniej
Gustavo Parrado
Ta odpowiedź nie jest pomocna, ponieważ posiadanie NavigationView ma wpływ na oryginalne pytanie, ponieważ jest potrzebne do przejścia do innego widoku.
JaseTheAce
16

Zobacz modyfikatory ułatwiające:

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

Przykład: wprowadź opis obrazu tutaj

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()
        }
    }
}
Peter Kreinz
źródło
4
Nie rozwiązuje problemu z wypychanym kontrolerem widoku.
damjandd
Wydaje się kluczowe, że modyfikator nie jest dodawany do NavigationView, ale do widoku wewnątrz. To sprawiło, że zaczęło działać. Dzięki! :-)
JaseTheAce
10

Jeśli ustawisz tytuł jako wbudowany dla widoku, w którym chcesz usunąć spację, nie trzeba tego robić w widoku z NavigationView, ale również nawigowanym.

.navigationBarTitle("", displayMode: .inline)

Wydanie początkowe rozwiązanie 1 następnie po prostu zmień wygląd pasków nawigacji

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

w widoku, który zawiera początkową NavigationView. ostateczne rozwiązanie

Jeśli chcesz zmienić wygląd z ekranu na ekran, zmień wygląd w odpowiednich widokach

MindBlower3
źródło
1
To rozwiązanie jest przydatne
Murilo Medeiros
9

Wypróbowałem również wszystkie rozwiązania wymienione na tej stronie i uznałem, że rozwiązanie @graycampbell działa dobrze, z dobrze działającymi animacjami. Próbowałem więc stworzyć wartość, której mogę po prostu używać w całej aplikacji, do której mam dostęp w dowolnym miejscu na przykładzie hackingwithswift.com

Stworzyłem ObservableObjectklasę

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

I w SceneDelegatepodobny sposób przejdź do widoku początkowego

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

Następnie ContentViewmożemy śledzić ten obiekt obserwowalny w ten sposób i utworzyć łącze do 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
            }
        }
    }
}

A potem, uzyskując dostęp do drugiego widoku (SomeView), ponownie go ukrywamy w ten sposób:

struct SomeView: View {
    @EnvironmentObject var navBarPrefs: NavBarPreferences

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

Aby podglądy działały, dodaj NavBarPreferences do podglądu w następujący sposób:

struct SomeView_Previews: PreviewProvider {
    static var previews: some View {
        SomeView().environmentObject(NavBarPreferences())
    }
}
Femkeo
źródło
2
używanie @EnvironmentObject jest znacznie lepsze do przekazywania danych w całej aplikacji niż @State , więc wolę, abyś odpowiadał więcej
Arafin Russell
7

Jest to błąd występujący w SwiftUI ( nadal w Xcode 11.2.1). Napisałem, ViewModifieraby to naprawić, na podstawie kodu z istniejących odpowiedzi:

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())
    }
}
Vatsal Manot
źródło
2
Dzięki temu gest „szybki do tyłu” już nie działa
Urkman
6

Możesz rozszerzyć natywny protokół View w następujący sposób:

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

Następnie zadzwoń np .:

ZStack {
    *YOUR CONTENT*
}
.hideNavigationBar()
Hopreeeen just
źródło
5

Dla mnie, ja stosujące .navigationBarTitlesię do NavigationViewi nie Listbył winowajcą. To działa dla mnie na 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)
        }
    }
}

Pasek nawigacji i lista bez przerwy na górze

Genki
źródło
5
Bez związku z zadanym pytaniem
Ahmed Sahib
3
@AhmedSahib Pytanie brzmiało „Jak usunąć domyślną przestrzeń paska nawigacji w SwiftUI NavigiationView” i mój kod to robi.
Genki
1
Doskonała rada. W moim rozwiązaniu musiałem zastosować dwa modyfikatory do wewnętrznej listy, aby pozbyć się odstępów: .navigationBarTitle ("", displayMode: .automatic) .navigationBarHidden (true) Następnie na zewnętrznym NavigationView musiałem zastosować: .navigationBarTitle (" TITLE ”, displayMode: .inline)
Frankenstein,
4

Dla mnie było to spowodowane wypychaniem mojego NavigationView z istniejącego. W efekcie jedno w drugim. Jeśli pochodzisz z NavigationView, nie musisz tworzyć jednego wewnątrz następnego, ponieważ jesteś już w NavigatonView.

RyanTCB
źródło
2

Podobna do odpowiedzi @graycampbell, ale trochę prostsza:

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

Ustawienie tytułu jest konieczne, ponieważ w przeglądanych widokach jest on wyświetlany obok przycisku Wstecz.

Fabian Streitel
źródło
1

SwiftUI 2

Istnieje specjalny modyfikator, który sprawia, że ​​pasek nawigacji zajmuje mniej miejsca:

.navigationBarTitleDisplayMode(.inline)

Nie trzeba już ukrywać paska nawigacji ani ustawiać jego tytułu.

pawello2222
źródło
0

Naprawdę podobał mi się pomysł podany przez @Vatsal Manot Aby stworzyć modyfikator do tego.
Usunięcie isHiddenwłaściwości z jego odpowiedzi, ponieważ nie wydaje mi się to przydatne, ponieważ sama nazwa modyfikatora sugeruje ukrywanie paska nawigacji.

// 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())
    }
}
Kiran Jasvanee
źródło
0

Miałem podobny problem podczas pracy nad aplikacją, w której TabView powinien być wyświetlany po zalogowaniu się użytkownika.

Jak zasugerował @graycampbell w swoim komentarzu, TabView nie powinien być osadzany w NavigationView, w przeciwnym razie pojawi się „puste miejsce”, nawet jeśli używasz .navigationBarHidden(true)

Użyłem, ZStackaby ukryć NavigationView. Zauważ, że w tym prostym przykładzie używam @Statei @Bindingdo zarządzania widocznością interfejsu użytkownika, ale możesz chcieć użyć czegoś bardziej złożonego, takiego jak obiekt środowiska.

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

Po naciśnięciu przycisku Zaloguj się strona początkowa znika i ładowany jest widok DetailView. Strona logowania pojawia się ponownie po przełączeniu przycisku Wyloguj

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")
            }
        }
    }
}
Sala
źródło
0

Moje rozwiązanie tego problemu było takie samo, jak sugerowane przez @Genki i @Frankenstein.

Zastosowałem dwa modyfikatory do wewnętrznej listy (NIE NavigationView), aby pozbyć się odstępów:

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

Na zewnętrznym NavigationView, a następnie zastosowany .navigationBarTitle("TITLE")do ustawienia tytułu.

Potwór z Loch Ness
źródło
1
To nic nie robi.
TruMan1
-8

Spróbuj NavigationViewwłożyć do środka GeometryReader.

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

Doświadczyłem dziwnego zachowania, gdy NavigationViewbył to widok główny.

Steven Marcotte
źródło