Search code examples
iosswiftuipageviewcontroller

UIPageViewController not calling delegate methods (Swift 5)


I have an extremely simple UIPageViewController setup in my project. When I create my page view controller and navigate to it, everything seems to work properly and it loads the first page properly. However, when I try to swipe or switch to other pages in the array of ViewControllers, my page controller does not call its delegate methods to do so.

Here is how I am instantiating and navigating to the page view controller from the parent view:

let tutorialPageVC = TareTutorialPageViewController()
let nc = UINavigationController(rootViewController: tutorialPageVC)
nc.modalPresentationStyle = .fullScreen
nc.definesPresentationContext = true
nc.setNavigationBarHidden(true, animated: true)
self.present(nc, animated: true, completion: nil)

And here is my page view controller class:

import UIKit

class TareTutorialPageViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {

    var tutorialPages = [UIViewController]()
    let pageControl = UIPageControl()

    override init(transitionStyle style: UIPageViewController.TransitionStyle, navigationOrientation: UIPageViewController.NavigationOrientation, options: [UIPageViewController.OptionsKey : Any]? = nil) {
        super.init(transitionStyle: .scroll, navigationOrientation: .horizontal, options: [:])

        self.dataSource = self
        self.delegate = self
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        let initialTutorialPage = 0

        let tutorialPage1 = TutorialPage1ViewController()
        let tutorialPage2 = TutorialPage2ViewController()
        let tutorialPage3 = TutorialPage3ViewController()

        self.tutorialPages.append(tutorialPage1)
        self.tutorialPages.append(tutorialPage2)
        self.tutorialPages.append(tutorialPage3)

        setViewControllers([tutorialPages[initialTutorialPage]], direction: .forward, animated: true, completion: nil)

        self.pageControl.frame = CGRect()
        self.pageControl.currentPageIndicatorTintColor = UIColor.white
        self.pageControl.pageIndicatorTintColor = UIColor.lightGray
        self.pageControl.numberOfPages = self.tutorialPages.count
        self.pageControl.currentPage = initialTutorialPage
        self.view.addSubview(self.pageControl)

        self.pageControl.translatesAutoresizingMaskIntoConstraints = false
        self.pageControl.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -5).isActive = true
        self.pageControl.widthAnchor.constraint(equalTo: self.view.widthAnchor, constant: -20).isActive = true
        self.pageControl.heightAnchor.constraint(equalTo: self.view.heightAnchor, constant: 20).isActive = true
        self.pageControl.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
    }

    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {

        //Might not need any of this if we only want the user to move forward??
        if let viewControllerIndex = self.tutorialPages.firstIndex(of: viewController)
        {
            if viewControllerIndex == 0
            {
                return self.tutorialPages.last
            }
            else
            {
                return self.tutorialPages[viewControllerIndex-1]
            }
        }

        return nil
    }

    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {

        if let viewControllerIndex = self.tutorialPages.firstIndex(of: viewController)
        {
            if viewControllerIndex < self.tutorialPages.count - 1
            {
                return self.tutorialPages[viewControllerIndex + 1]
            }
            else
            {
                //Navigation to go to scale tare settings here...
                //For now just returns to first page of page view

                return self.tutorialPages.first
            }
        }

        return nil
    }

    func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {

        if let viewControllers = pageViewController.viewControllers {
            if let viewControllerIndex = self.tutorialPages.firstIndex(of: viewControllers[0])
            {
                self.pageControl.currentPage = viewControllerIndex
            }
        }
    }


}

Here is an example of one my extremely simple view controllers that are shown by the page view controller:

import UIKit

class TutorialPage1ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        self.view.backgroundColor = UIColor.black

        let label = UILabel()
        label.text = "Tutorial page 1"
        label.textColor = UIColor.white
        self.view.addSubview(label)
        label.translatesAutoresizingMaskIntoConstraints = false
        label.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 50).isActive = true
        label.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 20).isActive = true
    }
}

Solution

  • The problem is not that the delegate funcs are not being called, the problem is that you are completely overlaying your UIPageViewController with a UIPageControl.

    You can confirm this by adding this line:

       // existing line
       self.view.addSubview(self.pageControl)
    
       // add this line
       self.pageControl.backgroundColor = .orange
    

    Change your constraint setup like this:

        // completely remove these lines
        //self.pageControl.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -5).isActive = true
        //self.pageControl.widthAnchor.constraint(equalTo: self.view.widthAnchor, constant: -20).isActive = true
        //self.pageControl.heightAnchor.constraint(equalTo: self.view.heightAnchor, constant: 20).isActive = true
        //self.pageControl.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
    
        let g = view.safeAreaLayoutGuide
    
        NSLayoutConstraint.activate([
            pageControl.widthAnchor.constraint(equalTo: g.widthAnchor, constant: -20.0),
            pageControl.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            pageControl.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -5.0),
        ])
    

    Now you should see the page control at the bottom, and you'll be able to swipe through the pages.

    By the way, UIPageViewController has a "built-in" UIPageControl that you might find works better anyway.