SwiftUI: как перейти с экрана C обратно на экран A (пропуская экран B) с помощью жеста пролистывания назад?

Я создаю приложение 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()
}

50
1

Ответ:

Решено

Предполагая, что вам нужно перейти к экрану 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)), но это приведет к потере жест смахивания назад. Жест якобы можно восстановить, но сам я это не пробовал.