使用 UINavigationController 推送新页面后未调用 OnChange

问题描述 投票:0回答:1

更新:首先,该项目需要支持iOS 14。

我正在开发一个混合项目(SwiftUI + UIKit),所以我必须使用 UINavigationController 作为窗口的根控制器。 (或者我无法推送任何 UIKitViewController)

根源是这样的:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let windowScene = scene as? UIWindowScene else {
            return
        }
        
        let window = UIWindow(windowScene: windowScene)
        window.makeKeyAndVisible()
        window.overrideUserInterfaceStyle = .light
        self.window = window

        //UIKit Navigation as root
        let rootView = AnyView(SwiftUIView1())
        let rootVC = UIHostingController(rootView: rootView)
        let rootNavC = UINavigationController(rootViewController: rootHubVC)
        window.rootViewController = rootNavC
}

在 SwiftUIView1 内部,它有一个 StateObject

class TestingState: ObservableObject, Equatable {
    static func == (lhs: TestingState, rhs: TestingState) -> Bool {
        lhs.currentJourneyLevel == lhs.currentJourneyLevel
    }
    
    @Published var currentJourneyLevel: Int {
        willSet {
            debugPrint("##### currentJourneyLevel will be set from \(currentJourneyLevel) to \(newValue)")
        }
    }
    
    init() {
        currentJourneyLevel = 1
    }
}

struct SwiftUIView1: View {
    @StateObject private var testingState = TestingState()
    @State var isActived = false {
        didSet {
            debugPrint("##### isActived: \(isActived)")
        }
    }

    var body: some View {
        VStack (spacing: 30) {
            Button("NextPage") {
                testingState.currentJourneyLevel = 2
            }
            
            NavigationLink(
                destination: SwiftUIView1(state: testingState),
                isActive: $isActived
            ) { EmptyView() }
        }
        .onChange(of: testingState.currentJourneyLevel) { currentJourneyLevel in
            debugPrint("##### currentJourneyLevel is onChange to: \(currentJourneyLevel)")
            isActived = currentJourneyLevel == 2
        }
        .navigationTitle("SwiftUIView1")
        .navigationBarTitleDisplayMode(.inline)
    }
}

在 SwiftUIView2 内部,它有另一个按钮可以将 currentJourneyLevel 设置回 1,以将其弹出到第一个视图,如下所示:

struct SwiftUIView2: View {
    private var state: TestingState

    init(state: TestingState) {
        self.state = state
    }
    
    var body: some View {
        ZStack {
            VStack(spacing: 10) {
                Button("Back") {
                    state.currentJourneyLevel = 1
                }
            }
            .navigationTitle("SwiftUIView2")
        }
    }
}

一旦我单击 NextPage 按钮,就按预期工作,currentJourneyLevel 更改为 2,然后调用 onChange 块,最后 isActived 将设置为 true,这是日志:

"##### currentJourneyLevel will be set from 1 to 2"
"##### currentJourneyLevel is onChange to: 2"
"##### isActived: true"

问题是: 当我尝试从第二个视图返回时,系统未按预期调用 onChange 块。

从日志中我可以看到 currentJourneyLevel 的值已正确更改,但似乎系统观察器不起作用,它没有调用应有的 onChange 方法。

"##### currentJourneyLevel will be set from 2 to 1"

但是,如果我更改为纯 SwiftUI 导航,如下所示:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let windowScene = scene as? UIWindowScene else {
            return
        }
        
        let window = UIWindow(windowScene: windowScene)
        window.makeKeyAndVisible()
        window.overrideUserInterfaceStyle = .light
        self.window = window
        
        //Pure SwiftUI Navigation
        let rootView = SwiftUIContainerView()
        let rootVC = UIHostingController(rootView: rootView)
        window.rootViewController = rootVC
        
        //UIKit Navigation as root
//        let rootView = AnyView(SwiftUIView1())
//        let rootVC = UIHostingController(rootView: rootView)
//        let rootNavC = UINavigationController(rootViewController: rootVC)
//        window.rootViewController = rootNavC
    }
struct SwiftUIContainerView: View {
    var body: some View {
        NavigationView {
            AnyView(SwiftUIView1())
        }
    }
}

一切都会顺利进行。单击下一页按钮:

"##### currentJourneyLevel will be set from 1 to 2"
"##### currentJourneyLevel is onChange to: 2"
"##### isActived: true"

单击返回按钮:

"##### currentJourneyLevel will be set from 2 to 1"
"##### currentJourneyLevel is onChange to: 1"
"##### isActived: false"

所以, 我应该怎么做才能使 onChange 块与 UIKitNavigationController 一起工作? 或者如果我犯了任何错误?

任何建议将不胜感激

swiftui uinavigationcontroller
1个回答
0
投票

将视图/控制器推送到 NavigationStack/UINavigationController 上时,它们覆盖的视图/控制器会在动画完成后从视图层次结构中删除(onDisappear/viewDidDisappear)。

如果您从堆栈内部使用

.onChange()
,则会产生奇怪的效果,因为仅当视图/控制器在屏幕上时才会调用闭包。我已经遇到过几次这种情况,需要将我的观察逻辑提取到一个仍然可见的地方,最好是在导航视图/控制器之外。

在我看来,你的

SwiftUIContainerView
最有潜力。

struct SwiftUIContainerView: View {
    
    let testingState: TestingState // might need to be a '@State var'
        
    var body: some View {
        NavigationStack {
            SwiftUIView1(state: testingState)
        }
        .onChange(testingState.currentJourneyLevel) { level in
            /// should be called regardless of the visibility of SwiftUIView1
        }
    }
}

另一个需要考虑的途径是 navigationDestination(for:) 修饰符 与 NavigationStack 上的路径参数协调。

enum Destinations: Hashable {
    case view2
}

如果您使用

Destinations
数组作为路径,则可以更轻松地观察哪些视图已被推入堆栈。

    @State private var path = [Destinations]()

    var body: some View {
        NavigationStack(path: $path) {
            SwiftUIView1(state: testingState)
                .navigationDestination(for: Destinations.self) { destination in
                     SwiftUIView2()
                }
        }
        .onChange(path.last == .view2) { isActive in
            /// called when a SwiftUIView2 is pushed/popped
        }
    }

nb:NavigationView 已被弃用,取而代之的是 NavigationStack。

(此外,所有代码都在浏览器中输入,未经编译器检查)

© www.soinside.com 2019 - 2024. All rights reserved.