web-dev-qa-db-fra.com

Bouton de retour personnalisé pour la barre de navigation de la vue de navigation dans SwiftUI

Je veux ajouter un bouton de navigation personnalisé qui ressemblera un peu à ceci:

desired navigation back button

Maintenant, j'ai écrit une vue BackButton personnalisée pour cela. Lors de l'application de cette vue en tant qu'élément de barre de navigation principale, procédez comme suit:

.navigationBarItems(leading: BackButton())

... la vue de navigation ressemble à ceci:

current navigation back button

J'ai joué avec des modificateurs comme:

.navigationBarItem(title: Text(""), titleDisplayMode: .automatic, hidesBackButton: true)

sans aucune chance.

Question

Comment puis-je...

  1. définir une vue utilisée comme bouton de retour personnalisé dans la barre de navigation? OU:
  2. replacer la vue par programme dans son parent?
    En optant pour cette approche, je pouvais masquer complètement la barre de navigation en utilisant .navigationBarHidden(true)
14
LinusGeffarth

TL; DR

Utilisez ceci pour faire la transition vers votre vue:

NavigationLink(destination: SampleDetails()) {}

Ajoutez ceci à la vue elle-même:

@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

Ensuite, dans une action de bouton ou quelque chose, fermez la vue:

presentationMode.wrappedValue.dismiss()

Code complet

Depuis un parent, naviguez à l'aide de NavigationLink

 NavigationLink(destination: SampleDetails()) {}

Dans DetailsView hide navigationBarBackButton et définissez le bouton de retour personnalisé au début navigationBatItem,

struct SampleDetails: View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

    var btnBack : some View { Button(action: {
        self.presentationMode.wrappedValue.dismiss()
        }) {
            HStack {
            Image("ic_back") // set image here
                .aspectRatio(contentMode: .fit)
                .foregroundColor(.white)
                Text("Go back")
            }
        }
    }

    var body: some View {
            List {
                Text("sample code")
        }
        .navigationBarBackButtonHidden(true)
        .navigationBarItems(leading: btnBack)
    }
}
24
Ashish

Swift 1.0

Il semble que vous pouvez maintenant combiner les navigationBarBackButtonHidden et .navigationBarItems pour obtenir l'effet que vous essayez d'obtenir.

Code

struct Navigation_CustomBackButton_Detail: View {
    @Environment(\.presentationMode) var presentationMode

    var body: some View {
        ZStack {
            Color("Theme3BackgroundColor")
            VStack(spacing: 25) {
                Image(systemName: "globe").font(.largeTitle)
                Text("NavigationView").font(.largeTitle)
                Text("Custom Back Button").foregroundColor(.gray)
                HStack {
                    Image("NavBarBackButtonHidden")
                    Image(systemName: "plus")
                    Image("NavBarItems")
                }
                Text("Hide the system back button and then use the navigation bar items modifier to add your own.")
                    .frame(maxWidth: .infinity)
                    .padding()
                    .background(Color("Theme3ForegroundColor"))
                    .foregroundColor(Color("Theme3BackgroundColor"))

                Spacer()
            }
            .font(.title)
            .padding(.top, 50)
        }
        .navigationBarTitle(Text("Detail View"), displayMode: .inline)
        .edgesIgnoringSafeArea(.bottom)
        // Hide the system back button
        .navigationBarBackButtonHidden(true)
        // Add your custom back button here
        .navigationBarItems(leading:
            Button(action: {
                self.presentationMode.wrappedValue.dismiss()
            }) {
                HStack {
                    Image(systemName: "arrow.left.circle")
                    Text("Go Back")
                }
        })
    }
}

Exemple

Voici à quoi ça ressemble (extrait du livre "SwiftUI Views"): SwiftUI Views Book Excerpt

6
Mark Moeykens

Basé sur d'autres réponses ici, ceci est une réponse simplifiée pour l'option 2 fonctionnant pour moi dans XCode 11.0:

struct DetailView: View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

    var body: some View {

        Button(action: {
           self.presentationMode.wrappedValue.dismiss()
        }) {
            Image(systemName: "gobackward").padding()
        }
        .navigationBarHidden(true)

    }
}

Remarque: Pour que la barre de navigation soit masquée, j'ai également dû définir, puis masquer la barre de navigation dans ContentView.

struct ContentView: View {
    var body: some View {
        NavigationView {
            VStack {
                NavigationLink(destination: DetailView()) {
                    Text("Link").padding()
                }
            } // Main VStack
            .navigationBarTitle("Home")
            .navigationBarHidden(true)

        } //NavigationView
    }
}
5
Ryan

Je suppose que vous souhaitez utiliser le bouton de retour personnalisé dans tous les écrans navigables, j'ai donc écrit un wrapper personnalisé basé sur la réponse @Ashish.

struct NavigationItemContainer<Content>: View where Content: View {
    private let content: () -> Content
    @Environment(\.presentationMode) var presentationMode

    private var btnBack : some View { Button(action: {
        self.presentationMode.wrappedValue.dismiss()
    }) {
        HStack {
            Image("back_icon") // set image here
                .aspectRatio(contentMode: .fit)
                .foregroundColor(.black)
            Text("Go back")
        }
        }
    }

    var body: some View {
        content()
            .navigationBarBackButtonHidden(true)
            .navigationBarItems(leading: btnBack)
    }

    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content
    }
}

Envelopper le contenu de l'écran dans NavigationItemContainer:

tilisation:

struct CreateAccountScreenView: View {
    var body: some View {
        NavigationItemContainer {
            VStack(spacing: 21) {
                AppLogoView()
                //...
            }
        }
    }
}
1
Igor Kasuan

Toutes les solutions que je vois ici semblent désactiver la fonctionnalité de balayage pour revenir à la page précédente, donc j'ai partagé une solution qui maintient cette fonctionnalité. Vous pouvez créer une extension de votre vue racine et remplacer votre style de navigation et appeler la fonction dans l'initialiseur de vue.

Exemple de vue

struct SampleRootView: View {

    init() {
        overrideNavigationAppearance()
    }

    var body: some View {
        Text("Hello, World!")
    }
}

Extension

extension SampleRootView {
   func overrideNavigationAppearance() {
        let navigationBarAppearance = UINavigationBarAppearance()
        let barAppearace = UINavigationBar.appearance()
        barAppearace.tintColor = *desired UIColor for icon*
        barAppearace.barTintColor = *desired UIColor for icon*

        navigationBarAppearance.setBackIndicatorImage(*desired UIImage for custom icon*, transitionMaskImage: *desired UIImage for custom icon*)

        UINavigationBar.appearance().standardAppearance = navigationBarAppearance
        UINavigationBar.appearance().compactAppearance = navigationBarAppearance
        UINavigationBar.appearance().scrollEdgeAppearance = navigationBarAppearance
   }
}

Le seul inconvénient de cette approche est que je n'ai pas trouvé de moyen de supprimer/modifier le texte associé au bouton de retour personnalisé.

0
Tyler Pashigian

Le balayage n'est pas désactivé de cette façon.

Travaille pour moi. XCode 11.3.1

Mettez ceci dans votre ViewController racine

init() {
    UINavigationBar.appearance().isUserInteractionEnabled = false
    UINavigationBar.appearance().backgroundColor = .clear
    UINavigationBar.appearance().barTintColor = .clear
    UINavigationBar.appearance().setBackgroundImage(UIImage(), for: .default)
    UINavigationBar.appearance().shadowImage = UIImage()
    UINavigationBar.appearance().tintColor = .clear
}

Et cela dans votre ViewController enfant

@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

Button(action: {self.presentationMode.wrappedValue.dismiss()}) {
    Image(systemName: "gobackward")
}
0
Kai Zheng

J'ai trouvé ceci: https://ryanashcraft.me/swiftui-programmatic-navigation/

Cela fonctionne, et cela peut jeter les bases d'une machine d'état pour contrôler ce qui est affiché, mais ce n'est pas aussi simple qu'avant.

import Combine
import SwiftUI

struct DetailView: View {
    var onDismiss: () -> Void

    var body: some View {
        Button(
            "Here are details. Tap to go back.",
            action: self.onDismiss
        )
    }
}

struct RootView: View {
    var link: NavigationDestinationLink<DetailView>
    var publisher: AnyPublisher<Void, Never>

    init() {
        let publisher = PassthroughSubject<Void, Never>()
        self.link = NavigationDestinationLink(
            DetailView(onDismiss: { publisher.send() }),
            isDetail: false
        )
        self.publisher = publisher.eraseToAnyPublisher()
    }

    var body: some View {
        VStack {
            Button("I am root. Tap for more details.", action: {
                self.link.presented?.value = true
            })
        }
            .onReceive(publisher, perform: { _ in
                self.link.presented?.value = false
            })
    }
}

struct ContentView: View {
    var body: some View {
        NavigationView {
            RootView()
        }
    }
}

If you want to hide the button then you can replace the DetailView with this:

struct LocalDetailView: View {
    var onDismiss: () -> Void

    var body: some View {
        Button(
            "Here are details. Tap to go back.",
            action: self.onDismiss
        )
            .navigationBarItems(leading: Text(""))
    }
}
0
grumblemo