Search code examples
iosswiftuipageviewcontroller

UIPageViewController with dynamic DataSource pages gets repeated


When I do fast swipe between pages sometimes I get duplicate pages. My index counting may be wrong, but I tried everything and always get the same.

DetailedProfileViewController.swift

class DetailedProfileViewController: UIViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {

    var currentProfileIndex : Int?
    var nextProfileIndex :Int?
    var data: Main?

    var mainPageView : UIPageViewController!

    override func viewDidLoad() {
        super.viewDidLoad()

        let profile = ProfileViewController()
        profile.profile = currentProfile()
        let arraysOfViews = [profile]

        mainPageView = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: [:])
        mainPageView.setViewControllers(arraysOfViews, direction: .forward, animated: true, completion: nil)
        mainPageView.dataSource = self
        mainPageView.delegate = self

        addChildViewController(mainPageView)
        view.addSubview(mainPageView.view)
        mainPageView.didMove(toParentViewController: self)

    }

    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        if currentProfileIndex == (data?.filteredProfiles.count)!-1 { return nil }
        nextProfileIndex = abs((currentProfileIndex! + 1) % (data?.filteredProfiles.count)!)
        let profile = ProfileViewController()
        profile.profile = data?.filteredProfiles[nextProfileIndex!]
        return profile
    }

    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        if currentProfileIndex == 0 { return nil }
        nextProfileIndex = abs((currentProfileIndex! - 1) % (data?.filteredProfiles.count)!)
        let profile = ProfileViewController()
        profile.profile = data?.filteredProfiles[nextProfileIndex!]
        return profile
    }

    func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
        if completed == true {
          currentProfileIndex = nextProfileIndex
        }
    }
}

I'm not setting all of View Controllers in viewDidLoad because it could be hundreds of them..


Solution

  • The dumbest solution is to add an index to ProfileViewController. If you do so, you can set it as zero to the first page. And whenever you are asked to provide the next or previous view controller, you always know relatively to what index, because you may extract it from the given pageViewController (which is in fact your ProfileViewController).

    Otherwise the handling of the currentProfileIndex may be very error-prone.

    UPDATE

    class DetailedProfileViewController: UIViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
    
        var data: Main?
    
        var mainPageView : UIPageViewController!
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            let profile = ProfileViewController()
            profile.index = 0
            profile.profile = currentProfile()
            let arraysOfViews = [profile]
    
            mainPageView = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: [:])
            mainPageView.setViewControllers(arraysOfViews, direction: .forward, animated: true, completion: nil)
            mainPageView.dataSource = self
            mainPageView.delegate = self
    
            addChildViewController(mainPageView)
            view.addSubview(mainPageView.view)
            mainPageView.didMove(toParentViewController: self)
    
        }
    
        func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
            guard let pagesCount = data?.filteredProfiles.count else {
                return nil
            }
    
            guard let newIndex = (viewController as? ProfileViewController).index + 1, newIndex < pagesCount else {
                return nil
            }
    
            let profile = ProfileViewController()
            profile.profile = data?.filteredProfiles[newIndex]
            profile.index = newIndex
            return profile
        }
    
        func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
            guard let pagesCount = data?.filteredProfiles.count else {
                return nil
            }
    
            guard let newIndex = (viewController as? ProfileViewController).index - 1, newIndex >= 0 else {
                return nil
            }
    
    
            let profile = ProfileViewController()
            profile.profile = data?.filteredProfiles[newIndex!]
            profile.index = newIndex
            return profile
        }
    }