Пользовательская кнопка «Назад» для панели навигации NavigationView в SwiftUI

Я хочу добавить пользовательскую кнопку навигации, которая будет выглядеть примерно так:

Пользовательская кнопка «Назад» для панели навигации NavigationView в SwiftUI

Теперь я написал специальное представление BackButton для этого. При применении этого представления в качестве ведущего элемента панели навигации выполните следующие действия:

.navigationBarItems(leading: BackButton())

...вид навигации выглядит так:

Пользовательская кнопка «Назад» для панели навигации NavigationView в SwiftUI

Я играл с такими модификаторами, как:

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

без везения.

Вопрос

Как я могу...

  1. установить представление, используемое в качестве пользовательской кнопки «Назад» на панели навигации? ИЛИ:
  2. программно вывести представление обратно к его родителю?
    При таком подходе я мог бы вообще скрыть панель навигации, используя .navigationBarHidden(true)

Улучшенная версия. (Свифт, iOS 13 бета 4) stackoverflow.com/questions/56853828/…

frogcjn 19.07.2019 09:01
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
57
1
75 412
12
Перейти к ответу Данный вопрос помечен как решенный

Ответы 12

Я нашел это: https://ryanashcraft.me/swiftui-programmatic-navigation/

Это работает, и может заложить основу для конечного автомата, чтобы контролировать то, что показывается, но это не так просто, как было раньше.

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

Вы знаете, что можете редактировать свои ответы? Не публикуйте новый, а нажмите маленькую Кнопка редактировать внизу вашего ответа. Затем удалите этот.

LinusGeffarth 12.07.2019 16:20

Улучшенная версия. (Свифт, iOS 13 бета 4) stackoverflow.com/questions/56853828/…

frogcjn 19.07.2019 09:02

Интересно, что меня взорвали за публикацию ссылки на то, что моя учетная запись была строго ограничена, но этот последний ответ проходит отлично.

John Endres 19.07.2019 16:31
Ответ принят как подходящий

TL;DR

Используйте это, чтобы перейти к к вашему представлению:

NavigationLink(destination: SampleDetails()) {}

Добавьте это к самому представлению:

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

Затем в действии кнопки или что-то в этом роде отклоните представление:

presentationMode.wrappedValue.dismiss()

Полный код

От родителя перейдите с помощью NavigationLink

 NavigationLink(destination: SampleDetails()) {}

В DetailsView скройте navigationBarBackButton и установите пользовательскую кнопку «Назад» на ведущую navigationBarItem,

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

это отлично работает, за исключением того, что PresentationMode.value теперь является PresentationMode.wrappedValue, однако это, похоже, отключает поведение прокрутки по умолчанию, чтобы вернуться назад. Любая идея о том, как включить его снова?

banana1 28.10.2019 10:53

Любая идея о том, как добавить салфетки обратно?

Andrei Matei 05.03.2020 11:06

Спасибо, работает нормально, но мы должны позаботиться о размере изображения. Всегда помните метод .resizable() для пользовательского размера кадра.

iGhost 13.03.2020 05:09

исправляет пролистывание: stackoverflow.com/a/60067845/196555

daihovey 25.09.2020 07:50

Когда вы хотите выполнить что-то .onAppear в Parent, с фактической навигацией по кнопке «Назад» это сработает. Но с вашей кнопкой возврата кода это не сработает

Anil 17.02.2021 19:08

положение кнопки «Назад» немного правильное по сравнению с ожидаемым, поэтому я должен использовать .offset(x, -10). Затем возникла другая проблема: нет ответа, если вы коснетесь кнопки «Назад», так как теперь у вас есть смещение. Как я могу решить проблему?

LiangWang 29.05.2021 23:47

@LiangWang Я обнаружил, что обертывание Button в HStack с отрицательным дополнением сохраняет сенсорную область неизменной. (Если вы установите фон для своей кнопки, вы увидите разницу по сравнению со смещением или дополнением самой кнопки.)

LordParsley 22.07.2021 09:10

При этом вы можете получить стрелку исходного размера: Image(systemName: "chevron.backward").imageScale(Image.Scale.large)

Laufwunder 16.10.2021 10:55

SwiftUI 1.0

Похоже, теперь вы можете комбинировать navigationBarBackButtonHidden и .navigationBarItems, чтобы получить эффект, которого вы пытаетесь достичь.

Код

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

Пример

Вот как это выглядит (отрывок из книги «SwiftUI Views»): SwiftUI Views Book Excerpt

Основываясь на других ответах здесь, это упрощенный ответ для варианта 2, работающего у меня в 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)

    }
}

Примечание. Чтобы панель навигации была скрыта, мне также нужно было установить, а затем скрыть панель навигации в ContentView.

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

        } //NavigationView
    }
}

Работал как шарм в XCode 11! Спасибо

Diego Navarro 03.11.2019 16:57

Я ожидаю, что вы захотите использовать пользовательскую кнопку «Назад» на всех навигационных экранах, поэтому я написал собственную оболочку на основе ответа @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
    }
}

Оберните содержимое экрана в NavigationItemContainer:

Применение:

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

Проблема в вашем CreateAccountScreenView. Если у вас есть другие элементы навигации в конце CreateAccountScreenView, то только конечные или ведущие элементы навигации. Это потому, что вы не можете определить navigationBarItems дважды, и работает только один

LiangWang 29.05.2021 23:50

Все решения, которые я вижу здесь, похоже, отключают функцию прокрутки, чтобы вернуться к предыдущей странице, поэтому делюсь найденным решением, которое поддерживает эту функциональность. Вы можете сделать расширение своего корневого представления и переопределить свой стиль навигации и вызвать функцию в инициализаторе представления.

Образец просмотра

struct SampleRootView: View {

    init() {
        overrideNavigationAppearance()
    }

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

Расширение

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

Единственным недостатком этого подхода является то, что я не нашел способа удалить/изменить текст, связанный с настраиваемой кнопкой «Назад».

Пролистывание таким образом не отключается.

Работает на меня. X-код 11.3.1

Поместите это в свой корневой вид

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
}

И это в вашем детском представлении

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

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

Спасибо! Это отличное решение для iOS 13 и iOS 14. Работа в Xcode 12.5.1.

Esteban 08.07.2021 21:32

Это решение работает для iPhone. Однако для iPad это не сработает из-за splitView.

import SwiftUI

struct NavigationBackButton: View {
  var title: Text?
  @Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>

  var body: some View {
    ZStack {
      VStack {
        ZStack {
          HStack {
            Button(action: {
              self.presentationMode.wrappedValue.dismiss()
            }) {
              Image(systemName: "chevron.left")
                .font(.title)
                .frame(width: 44, height: 44)
              title
            }
            Spacer()
          }
        }
        Spacer()
      }
    }
    .zIndex(1)
    .navigationBarTitle("")
    .navigationBarHidden(true)
  }
}

struct NavigationBackButton_Previews: PreviewProvider {
  static var previews: some View {
    NavigationBackButton()
  }
}

Действительно простой метод. Всего две строки кода ?

@Environment(\.presentationMode) var presentationMode
self.presentationMode.wrappedValue.dismiss()

Пример:

import SwiftUI

struct FirstView: View {
    @State var showSecondView = false
    
    var body: some View {
        NavigationLink(destination: SecondView(),isActive : self.$showSecondView){
            Text("Push to Second View")
        }
    }
}


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

    var body : some View {    
        Button(action:{ self.presentationMode.wrappedValue.dismiss() }){
            Text("Go Back")    
        }    
    }
}

Вы можете использовать UIAppearance для этого:

if let image = UIImage(named: "back-button") {
    UINavigationBar.appearance().backIndicatorImage = image
    UINavigationBar.appearance().backIndicatorTransitionMaskImage = image
}

Это должно быть добавлено в ваше приложение на ранней стадии, например App.init. Это также сохраняет встроенную функцию прокрутки назад.

Вот более сжатая версия, использующая принципы, показанные в других комментариях, чтобы изменить только текст кнопки. Значок chevron.left также можно легко заменить другим значком.

Создайте свою собственную кнопку, а затем назначьте ее с помощью .navigationBarItems(). Я обнаружил, что следующий формат наиболее близок к кнопке «Назад» по умолчанию.

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

    var backButton : some View {
        Button(action: {
            self.presentationMode.wrappedValue.dismiss()
        }) {
            HStack(spacing: 0) {
                Image(systemName: "chevron.left")
                    .font(.title2)
                Text("Cancel")
            }
        }
    }

Убедитесь, что вы используете .navigationBarBackButtonHidden(true), чтобы скрыть кнопку по умолчанию и заменить ее своей собственной!

        List(series, id:\.self, selection: $selection) { series in
            Text(series.SeriesLabel)
        }
        .navigationBarBackButtonHidden(true)
        .navigationBarItems(leading: backButton)

Это работает хорошо, но есть ли лучший способ, чтобы backButton можно было использовать в нескольких представлениях?

BadmintonCat 18.03.2022 07:10

Просто напишите это:

import SwiftUI

struct ContentView: View {
    var body: some View {
        NavigationView {

        }.onAppear() {
            UINavigationBar.appearance().tintColor = .clear
            UINavigationBar.appearance().backIndicatorImage = UIImage(named: "back")?.withRenderingMode(.alwaysOriginal)
            UINavigationBar.appearance().backIndicatorTransitionMaskImage = UIImage(named: "back")?.withRenderingMode(.alwaysOriginal)
        }
    }
}

это как бы «работает», но новое изображение кнопки «Назад» не выровнено правильно. Код может нуждаться в некоторых корректировках. Кроме того, может быть, было бы лучше поместить его в init() {}?

MVZ 07.03.2022 13:05

Другие вопросы по теме