Я создаю приложение SwiftUI, в котором у меня есть три экрана (A, B и C), на которые осуществляется последовательная навигация. Поток выглядит следующим образом:
Screen A -> Screen B -> Screen C
Я хочу добиться следующего поведения:
Когда пользователь проводит назад с экрана C, он должен быть перенаправлен непосредственно на экран A, пропуская экран B (родное пролистывание назад для iOS).
На экране B вместо использования NavigationPath.append(viewC) я попытался установить путь следующим образом:
navigationPath = [.viewA, viewC]
Это приводит к полному отсутствию анимации при переходе к экрану C.
я тоже пробую это
{
navigationPath.removeLast
navigationPath.append(viewC)
}
Это приводит к полному отсутствию анимации при переходе к экрану C.
При появлении экрана C я попытался сбросить путь следующим образом:
navigationPath = [.viewA, viewC]
Здесь я получаю анимацию при переходе на экран C, когда работает onAppear, я видел анимацию обратного перехода (от viewC к viewC).
Есть ли лучший способ добиться такого поведения, при котором пролистывание назад с экрана C напрямую возвращается на экран A без каких-либо непреднамеренных анимаций? Любой совет будет принят с благодарностью!
Пример кода:
import SwiftUI
enum Route {
case viewA, viewB, viewC
}
struct ContentView: View {
@State private var path: [Route] = []
var body: some View {
NavigationStack(path: $path) {
Text("This is root view")
Button {
path.append(.viewA)
} label: {
Text("Go to screen A")
}
.navigationDestination(for: Route.self) { route in
switch route {
case .viewA:
AView(path: $path)
case .viewB:
BView(path: $path)
case .viewC:
CView(path: $path)
}
}
}
}
}
struct AView: View {
@Binding var path: [Route]
var body: some View {
VStack {
Text("This is Screen A")
Button("Go to Screen B") {
path.append(.viewB)
}
}
}
}
struct BView: View {
@Binding var path: [Route]
var body: some View {
VStack {
Text("This is Screen B")
Button("Go to Screen C") {
path = [.viewA, .viewC]
}
}
}
}
struct CView: View {
@Binding var path: [Route]
var body: some View {
VStack {
Text("This is Screen C")
}
}
}
#Preview {
ContentView()
}
Предполагая, что вам нужно перейти к экрану C с анимацией, но без двойной анимации при переходе на экран C, я считаю, что следующее должно помочь:
struct BView: View {
@Binding var path: [Route]
var body: some View {
VStack {
Text("This is Screen B")
Button("Go to Screen C") {
path.append(.viewC)
}
}
.onDisappear { // <-- Here, add .onDisappear modifier to screen B
var transaction = Transaction()
transaction.disablesAnimations = true
withTransaction(transaction) { // <-- Here, wrap path mutation in withTransaction, where transaction has disablesAnimations set to true
path = [.viewA, .viewC]
}
}
}
}
Вы были на правильном пути и пытались изменить путь после исчезновения экрана B, но это привело к двойной анимации экрана C: первый раз, когда он появился естественным образом, и второй раз, когда путь был сброшен как часть .onDisappear. действие экрана Б.
Судя по всему, начиная с iOS 16.2, вы можете отключить анимацию при нажатии и выталкивании NavigationStack, как показано выше. Этот ответ я нашла здесь.
Хотя двойная навигация по-прежнему происходит, как описано выше, она не будет заметна для пользователя. Вы можете увидеть это в действии, если присвоите каждому экрану заголовок, а затем заметите, что заголовок кнопки «Назад» меняется на заголовок экрана A после перехода на экран C (и после исчезновения экрана B).
Так что это может быть не идеально, если вы используете собственные заголовки навигации для каждого экрана.
Альтернативой этому подходу является скрытие кнопки «Назад» на экране C с помощью .navigationBarBackButtonHidden(true)
и установка пользовательской кнопки «Назад» на панели инструментов с той же логикой, что и у кнопки на экране C (что-то вроде path = removeLast(2)
), но это приведет к потере жест смахивания назад. Жест якобы можно восстановить, но сам я это не пробовал.