首页 > 解决方案 > UIPageViewController 在 SwiftUI 中滑动 1 次后停止工作

问题描述

我正在关注 SwiftUI 的 Apple 教程,并在“与 UIKit 交互”( https://developer.apple.com/tutorials/swiftui/interfacing-with-uikit ) 下制作了一个 UIPageController。

在教程中,他们使用 State 属性来currentPage更新PageView. 我想进行一些实验,并决定将其嵌入到PageView不同的视图中,并currentPage在该父视图中进行属性更新。因此,很自然地,我将@State属性更改为@Binding属性PageView,以便在父级中进行更新currentPage

但是,当我这样做时, UIPageController 停止工作并继续nilpageViewController(_:viewControllerBefore:) -> UIViewController?第二次滑动返回。

这是代码,我将添加注释以使其不那么繁琐:

struct ContentView: View {
    @State var currentPage = 0  // This is the new @State variable which should get updated.

    var body: some View {
        VStack {
            PageView([Text("First page"), Text("Second Page"), Text("Third Page")],
                      currentPage: self.$currentPage)  // Passed a binding to the State variable since I want that to be updated when the page changes.
            Text("Page number: \(currentPage)")  // Removing this fixes the problem, but I need it here.
        }
    }
}
struct PageView<Page: View>: View {
    var viewControllers: [UIHostingController<Page>]
    @Binding var currentPage: Int  // This used to be an @State property, but I changed it to a binding since the currentPage variable is now being updated elsewhere (In the view above).

    init(_ views: [Page], currentPage: Binding<Int>) {
        self.viewControllers = views.map { UIHostingController(rootView: $0) }  // Simply wrapping all the views inside ViewControllers for UIPageViewController to use.
        self._currentPage = currentPage  // Assigning the binding.
    }

    var body: some View {
        PageViewController(controllers: self.viewControllers, currentPage: self.$currentPage)
    }
}
struct PageViewController: UIViewControllerRepresentable {
    var controllers: [UIViewController]
    @Binding var currentPage: Int

    func makeCoordinator() -> Coordinator {  // Makes the coordinator, irrelevant to the problem
        Coordinator(self)
    }
    func makeUIViewController(context: Context) -> UIPageViewController { 
        // I don't think the problem lies here. 
        let pageViewController = UIPageViewController(
            transitionStyle: .scroll,
            navigationOrientation: .horizontal)

        pageViewController.dataSource = context.coordinator
        pageViewController.delegate = context.coordinator

        return pageViewController
    }
    func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
        // I don't think the problem lies here. 
        pageViewController.setViewControllers(
            [self.controllers[currentPage]], direction: .forward, animated: true)
    }

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

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

        func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
            // This is where I think the problem lies. When the line below is evaluated, nil is returned on the second swipe when it shouldn't be. It works on the first swipe but on the second swipe nil is returned and therefore nothing is presented.
            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 == parent.controllers.count - 1 {
                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
            }
        }
    }
}

此外,从解决问题中删除,但我需要它TextContentView- 所以删除它不是一个选项。

如果有人可以解释如何解决我的问题,我将非常感激。如果您太懒惰,则无需提供代码示例,简单的解释也应该可以完成工作。

提前致谢!

标签: iosswiftuikitswiftuiuipageviewcontroller

解决方案


刷新时在PageView更新期间重新创建。避免这种情况的解决方案是确认。bodyTextPageViewEquatable

最简单的修复演示如下(仅修改部分)。使用 Xcode 11.4 / iOS 13.4 测试

注意:使用相同的 PageView,因为没有提供 PageView2

// ...
var body: some View {
    VStack {
        PageView([Text("First page"), Text("Second Page"), Text("Third Page")],
                  currentPage: self.$currentPage)
                  .equatable()                // << here !!
        Text("Page number: \(currentPage)")
    }
}


struct PageView<Page: View>: View, Equatable { // << confirm !!
   static func == (lhs: PageView<Page>, rhs: PageView<Page>) -> Bool {
       return true // just for demo, make it meaningful on some property
   }

   // .. other code goes here

推荐阅读