UIPageViewController stops working after 1 swipe in SwiftUI

时间秒杀一切 提交于 2021-02-10 15:15:25

问题


I was following the Apple tutorial for SwiftUI and under 'Interfacing with UIKit' (https://developer.apple.com/tutorials/swiftui/interfacing-with-uikit) we were made to make a UIPageController.

In the tutorial, they used a State property to update the currentPage variable in PageView. I wanted to experiment a little bit and decided that I want to embed the PageView in a different view and have a currentPage property update in that parent view instead. So, naturally, I changed the @State property to an @Binding property in PageView in order to update currentPage in the parent.

However, when I did that, the UIPageController stopped working and kept returning nil from pageViewController(_:viewControllerBefore:) -> UIViewController? on the second swipe.

This is the code, I'll add comments to make it less tedious to understand:

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
            }
        }
    }
}

Also, removing the Text from ContentView fixes the issue but I need it there - so removing it is not an option.

If someone can explain how to fix my issue I'll be extremely grateful. No need to provide a code sample if you're too lazy, a simple explanation should also get the job done.

Thanks in advance!


回答1:


The PageView is recreated during update of body when Text is refreshed. The solution to avoid this is to make PageView confirm to Equatable.

The simplest demo of fix is as follows (only modified parts). Tested with Xcode 11.4 / iOS 13.4

Note: used same PageView, because PageView2 is not provided

// ...
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


来源:https://stackoverflow.com/questions/62262668/uipageviewcontroller-stops-working-after-1-swipe-in-swiftui

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!