如何更改@environment变量导致生成新的viewController之后,如何在SwiftUI中更新PageViewController

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

在我的SwiftUI应用中,我目前有一个使用UIKit实现的PageViewController。它遵循传统的SwiftUI-Apple的SwiftUI UIKit集成tutorials中概述的UIKit实现。

我有一个数据,该数据填充在一个由@Environment变量提供的,传递给PageViewController的controllers数组中的UIViewController。在应用程序的另一个屏幕中,您可以发出导致环境对象更新的操作,从而触发驻留在PageViewController中的ViewController的重新渲染。

但是,此重新渲染会导致问题,因为用新标识符重新构造了ViewController,因此在类协调器pageViewController函数的parent.controller数组中找不到viewController的索引。这将导致索引默认为nil并禁用对更新的viewController的任何滑动。我仍然能够使用页面控制点在viewControllers之间导航,但我想确定如何更新parent.controller数组以包括新的ViewController并丢弃旧的ViewController。

经过数小时的搜索,调试和修改后,我无法找到一种方法来重置父控制器数组,新的ViewController替换了已丢弃的旧视图。下面是PageViewController的代码。

import SwiftUI
import UIKit

struct PageViewController: UIViewControllerRepresentable {
    var controllers: [UIViewController]
    @Binding var currentPage: Int

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    func makeUIViewController(context: Context) -> UIPageViewController {
        let pageViewController = UIPageViewController(
            transitionStyle: .scroll,
            navigationOrientation: .horizontal)
        pageViewController.view.backgroundColor = UIColor.clear
        pageViewController.dataSource = context.coordinator
        pageViewController.delegate = context.coordinator
        return pageViewController
    }

    func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
        pageViewController.setViewControllers(
            [controllers[currentPage]], direction: .forward, animated: false)
    }

    class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
        var parent: PageViewController

        init(_ pageViewController: PageViewController) {
            self.parent = pageViewController
        }

        func pageViewController(
            _ pageViewController: UIPageViewController,
            viewControllerBefore viewController: UIViewController) -> UIViewController? {
            print(parent.controllers)
            print(viewController)
            guard let index = parent.controllers.firstIndex(of: viewController) else {
                return nil
            }
            if index == 0 {
                return parent.controllers.last
            }
            return parent.controllers[index - 1]
        }

        func pageViewController(
            _ pageViewController: UIPageViewController,
            viewControllerAfter viewController: UIViewController) -> UIViewController? {
            guard let index = parent.controllers.firstIndex(of: viewController) else {
                return nil
            }
            if index + 1 == parent.controllers.count {
                return parent.controllers.first
            }
            return parent.controllers[index + 1]
        }

        func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
            if completed,
                let visibleViewController = pageViewController.viewControllers?.first,
                let index = parent.controllers.firstIndex(of: visibleViewController) {
                parent.currentPage = index
            }
        }
    }
}

更新环境状态后,该状态将填充给定ViewController的数据,从而导致重新渲染,coordinator类可以在以下带注释的代码中打印包含旧ViewController和新ViewController的控制器数组,但我没有还能够找到一种可靠的方法来确保新的ViewController有效地替代旧的ViewController。

struct PageViewController: UIViewControllerRepresentable {
     ...

    class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate { 
        ...

        func pageViewController(
            _ pageViewController: UIPageViewController,
            viewControllerBefore viewController: UIViewController) -> UIViewController? {
            print(parent.controllers)
            // prints out array of ViewControllers including old ViewController that has now been 
            // discarded 
            // [<_TtGC7SwiftUI19UIHostingControllerVS_7AnyView_: 0x7fd595c93de0>, 
            // <_TtGC7SwiftUI19UIHostingControllerVS_7AnyView_: 0x7fd595c94be0>, 
            // <_TtGC7SwiftUI19UIHostingControllerVS_7AnyView_: 0x7fd595c96830>, 
            // <_TtGC7SwiftUI19UIHostingControllerVS_7AnyView_: 0x7fd595c976b0>]
            print(viewController)
            // prints out new ViewController that does not exist within the parent.controllers array
            // and hence nil is returned from the guard
            // <_TtGC7SwiftUI19UIHostingControllerVS_7AnyView_: 0x7fd593721710>
            guard let index = parent.controllers.firstIndex(of: viewController) else {
                return nil
            }
            if index == 0 {
                return parent.controllers.last
            }
            return parent.controllers[index - 1]
        }

        ...
}

对此问题的任何帮助或指导,将不胜感激!

uiviewcontroller swiftui uipageviewcontroller
1个回答
0
投票

不可避免地在将问题发布到StackOverflow的几分钟内,我已经找到了解决问题的方法。

移动将viewControllers从updateUIViewController()设置为makeUIViewController()的逻辑可确保在创建新的ViewController时调用该方法,而不是在updateUIViewController()内仅调用一次该方法

通过执行以下更改,现在可以在更新时恢复刷卡功能,因为可以找到新创建的ViewController的索引。

以下是PageViewController的完整代码,它可以处理正在更新的控制器:

import SwiftUI
import UIKit

struct PageViewController: UIViewControllerRepresentable {
    var controllers: [UIViewController]
    @Binding var currentPage: Int

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    func makeUIViewController(context: Context) -> UIPageViewController {
        let pageViewController = UIPageViewController(
            transitionStyle: .scroll,
            navigationOrientation: .horizontal)
        pageViewController.view.backgroundColor = UIColor.clear
        pageViewController.dataSource = context.coordinator
        pageViewController.delegate = context.coordinator

        pageViewController.setViewControllers(
            [controllers[currentPage]], direction: .forward, animated: false)

        return pageViewController
    }

    func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
    }

    class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
        var parent: PageViewController

        init(_ pageViewController: PageViewController) {
            self.parent = pageViewController
        }

        func pageViewController(
            _ pageViewController: UIPageViewController,
            viewControllerBefore viewController: UIViewController) -> UIViewController? {
            guard let index = parent.controllers.firstIndex(of: viewController) else {
                return nil
            }
            if index == 0 {
                return parent.controllers.last
            }
            return parent.controllers[index - 1]
        }

        func pageViewController(
            _ pageViewController: UIPageViewController,
            viewControllerAfter viewController: UIViewController) -> UIViewController? {
            print(pageViewController)
            guard let index = parent.controllers.firstIndex(of: viewController) else {
                return nil
            }
            if index + 1 == parent.controllers.count {
                return parent.controllers.first
            }
            return parent.controllers[index + 1]
        }

        func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
            if completed,
                let visibleViewController = pageViewController.viewControllers?.first,
                let index = parent.controllers.firstIndex(of: visibleViewController) {
                parent.currentPage = index
            }
        }
    }
}

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