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

Dan*_*lay 5 uiviewcontroller uipageviewcontroller swiftui

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

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

然而,这种重新渲染会导致问题,因为 ViewController 是用新标识符重新制作的,因此parent.controller在类协调器 pageViewController 函数内的数组中找不到 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
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在更新为给定 ViewController 填充数据的环境状态后,导致重新渲染,协调器类可以打印控制器数组,其中包括旧 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]
        }

        ...
}

Run Code Online (Sandbox Code Playgroud)

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

Fab*_*yne 9

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

似乎这是一个错误。如果您查看 UIViewControllerRepresentable 循环,它应该从 Init > Coordinator > makeUIViewController > updateUIViewController 开始。

但是如果 PageViewController 中的页面得到更新(我们的案例)。页面由 SwiftUI 重新创建,很棒,但是 PageViewController 会从 Init > updateUIViewController 获得一个内部错误的创建周期。没有 makeUIViewController 既没有协调器。以某种方式创建了一个新的 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: $0) }
        self._currentPage = currentPage
    }

    var body: some View {
        PageViewController(controllers: viewControllers, currentPage: $currentPage).id(UUID())
    }
}
Run Code Online (Sandbox Code Playgroud)

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


Lor*_*ngo 5

认为问题出在协调员身上:

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

context.coordinator.parent = self
Run Code Online (Sandbox Code Playgroud)

另外,请记住,pageViewController.setViewControllers将会发生动画,因此您必须将animated设置为false,或者正确处理它。

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