Dynamic number of pages in UIPageViewController

Viewed 266

I'm working on an app which is highly dependent on page views. In short the app presents one page for each week in a year. You change week by swiping the page or by selecting a specific week number. I have been testing many different page view solutions, including TabView (PageTabViewStyle), but in most cases the performance is really bad. Because of this I decided to try a UIKit solution described here, by Apple.

This solutions works like a charm in every way, except for one. In my app you can also select the year. This means that the range of weeks can be 1-52 or 1-53 and I can't seem to get the UIPageViewController to update the number of pages (weeks) when I change year.

In short, my current code looks something like this:
I have a state which keeps initial weeks for the current year in an array:

@State var weeks: [Int] = Array(0..<Date.lastWeekOfYear(year: Date.currentYear(date: Date())))

And the page view is setup like this:

PageView(page: self.$page, pages: weeks.map {
  ListView(year: self.$year, week: $0 )
})

When the year changes, I update the weeks state with a new array in an .onChange. But as I mentioned, the number of pages does not update.

I've seen a lot of suggestions here at SO but non of them seems to work. If you have an idea of how solve this, please note that I'm fairly new to SwiftUI and I have never worked with UIKit before, so please be detailed in your answer.

The PageViewController code from Apples guide looks like this:

import SwiftUI
import UIKit

struct PageViewController<Page: View>: UIViewControllerRepresentable {
    var pages: [Page]
    @Binding var currentPage: Int

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

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

        return pageViewController
    }

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

    class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
        var parent: PageViewController
        var controllers = [UIViewController]()

        init(_ pageViewController: PageViewController) {
            parent = pageViewController
            controllers = parent.pages.map { UIHostingController(rootView: $0) }
        }

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

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

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

Many thanks!

0 Answers
Related