首页 > 解决方案 > 视图之间的转换

问题描述

我的应用程序中的视图之间的转换有一个小问题。这是我用来处理导航的代码:

class NavigationStack: ObservableObject {
fileprivate private(set) var navigationType: NavigationType = .push
fileprivate private(set) var navigationTransition: NavigationTransition = .scale


private var viewStack = ViewStack() {
    didSet {
        currentView = viewStack.peek()
    }
}
@Published fileprivate var currentView: ViewElement?


func push<Element: View>(_ element: Element, withAnimation animation: NavigationTransition = .scale, withId identifier: String? = nil) {
    navigationTransition = animation
    withAnimation(navigationTransition.animation) {
        navigationType = .push
        viewStack.push(ViewElement(id: identifier == nil ? UUID().uuidString : identifier!,
                                   wrappedElement: AnyView(element)))
    }
}


func pop(to: PopDestination = .previous) {
    withAnimation(navigationTransition.animation) {
        navigationType = .pop
        switch to {
        case .root:
            viewStack.popToRoot()
        case .view(let viewId):
            viewStack.popToView(withId: viewId)
        default:
            viewStack.popToPrevious()
        }
    }
}


private struct ViewStack {
    private var views = [ViewElement]()

    func peek() -> ViewElement? {
        views.last
    }

    mutating func push(_ element: ViewElement) {
        guard indexForView(withId: element.id) == nil else {
            print("Duplicated view identifier: \"\(element.id)\". You are trying to push a view with an identifier that already exists on the navigation stack.")
            return
        }
        views.append(element)
    }

    mutating func popToPrevious() {
        _ = views.popLast()
    }

    mutating func popToView(withId identifier: String) {
        guard let viewIndex = indexForView(withId: identifier) else {
            print("Identifier \"\(identifier)\" not found. You are trying to pop to a view that doesn't exist.")
            return
        }
        views.removeLast(views.count - (viewIndex + 1))
    }

    mutating func popToRoot() {
        views.removeAll()
    }

    private func indexForView(withId identifier: String) -> Int? {
        views.firstIndex {
            $0.id == identifier
        }
    }
}

private struct ViewElement: Identifiable, Equatable {
    let id: String
    let wrappedElement: AnyView


    static func == (lhs: ViewElement, rhs: ViewElement) -> Bool {
        lhs.id == rhs.id
    }
}

struct NavigationStackView<Root>: View where Root: View {
@ObservedObject private var navViewModel: NavigationStack = NavigationStack()
@ObservedObject private var popupViewModel: PopupManager = PopupManager()
private let rootViewID = "root"
private let rootView: Root


init(@ViewBuilder rootView: () -> Root) {
    self.rootView = rootView()
}


var body: some View {
    let showRoot = navViewModel.currentView == nil
    let navigationType = navViewModel.navigationType
    let transitions = navViewModel.navigationTransition.transitions

    let showPopup = popupViewModel.openPopup
    let popup = popupViewModel.popup


    return ZStack {
        Group {
            if showRoot {
                rootView
                    .id(rootViewID)
                    .transition(navigationType == .push ? transitions.push : transitions.pop)
                    .environmentObject(navViewModel)
                    .environmentObject(popupViewModel)
            } else {
                navViewModel.currentView!.wrappedElement
                    .id(navViewModel.currentView!.id)
                    .transition(navigationType == .push ? transitions.push : transitions.pop)
                    .environmentObject(navViewModel)
                    .environmentObject(popupViewModel)
            }
        }

        if showPopup {
            popup
                .environmentObject(popupViewModel)
                .ignoresSafeArea()
        }
    }
}

上面的代码按以下方式工作:我将 NavigationStack 包含到第一个屏幕,然后,当我想更改屏幕时,我使用 @EnvironmentObject NavigationStack 值及其方法 push。问题是当我想改变屏幕之间的过渡动​​画时(假设我想使用幻灯片而不是缩放),即将离开的屏幕仍然保持“旧”过渡(这是因为当 currentView 正在改变时,没有办法访问前一个视图并更改其转换)。消除此错误的唯一方法是在我要更改的视图中声明局部变量 @State transition: AnyTransition,并在调用方法 push 之前更改它:

@State private var transition: AnyTransition = .opacity

var body: some View {
    ZStack {}
        .transition(transition)
    }

func openView() {
    transition = .slide
    navigationStack.push(newView(), withAnimation: .slide)
}

但我想实现对 NavigationStack(View) 类的更改。还有我的问题 - 有没有办法处理“旧”视图?我将非常感谢任何提示!

标签: swiftswiftui

解决方案


推荐阅读