更新 @EnvironmentObject var 以将数据传递给 SwiftUI 中的 PageViewController 会导致 ViewController 之间的滑动丢失

Updating an @EnvironmentObject var to pass data to PageViewController in SwiftUI results in loss of swiping between ViewControllers

在我的 SwiftUI 应用程序中,我目前有一个使用 UIKit 实现的页面ViewController。它遵循传统的 SwiftUI - Apple 的 SwiftUI UIKit 集成 tutorials.

中概述的 UIKit 实现

我有填充 UIViewController 的数据,该数据位于传递给 PageViewController 的控制器数组中,由 @Environment 变量提供。在应用程序的另一个屏幕中,您可以发出导致环境对象更新的操作,触发重新呈现位于 PageViewController.

中的 ViewController

但是,此重新呈现会导致问题,因为 ViewController 是使用新标识符重新制作的,因此无法在 [=13= 中找到 viewController 的索引] 数组在 Class 协调器页面 ViewController 函数中。这会导致索引默认为 nil 并禁用更新后的 viewController 上的任何滑动。我仍然可以使用页面控制点在 viewController 之间导航,但我想确定如何更新 parent.controller 数组以包含新的 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,导致重新渲染,协调器 class 可以打印包含旧 [=30= 的控制器数组] 以及下面注释代码中的新 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]
        }

        ...
}

非常感谢有关此问题的任何帮助或指导!

遇到同样的问题,最后几个小时,我找到了解决方法。

看来是个bug。如果您查看 UIViewControllerRepresentable 循环,它应该从 Init > Coordinator > makeUIViewController > updateUIViewController 开始。

但是如果 PageViewController 中的页面得到更新(我们的案例)。页面由 SwiftUI 重新创建,很好,但是 PageViewController 从 Init > updateUIViewController 得到一个内部错误的创建周期。没有 makeUIViewController 既没有 Coordinator。以某种方式创建了一个新的 UIPageViewController(如何?)导致您注意到的差异。

要解决此问题并强制重新创建 PageViewController,请在您的页面视图代码中添加 .id(UUID),例如:

struct SGPageView<Page: View>: View {
    var viewControllers: [UIHostingController<Page>]
    @Binding var currentPage:Int

    init(_ views: [Page], currentPage:Binding<Int>) {
        self.viewControllers = views.map { UIHostingController(rootView: [=10=]) }
        self._currentPage = currentPage
    }

    var body: some View {
        PageViewController(controllers: viewControllers, currentPage: $currentPage).id(UUID())
    }
}

它会起作用,但您会注意到页面的滚动位置已重置。另一个错误 ;)

认为问题出在协调器上:

当您在 pageviewcontroller 中更新控制器时,Coordinator 的 parent 属性 保持不变(因为它是一个结构,所以他的所有属性)。所以你可以简单地在你的 updateUIViewController 方法中添加这行代码:

context.coordinator.parent = self

另外,记住pageViewController.setViewControllers的动画会出现,所以你要设置animated为false,或者妥善处理。

还有很多其他方法可以解决这个问题(我写了最直观的解决方案), 重要的是要知道错误来自哪里。