Caching page view controllers in UIPageViewController


#1

I’ve seen a number of tutorials on the web (e.g., this and this) where a UIPageViewControllerDataSource keeps returning new UIViewController instances for the pages of a UIPageViewController.

This is problematic as those view controllers leak. If you have event handlers that you’re setting up on the pages themselves, you will start getting duplicates.

Instead, you should be using an object pool and returning cached view controllers whenever possible form the _:viewControllerBeforeViewController:_: and :_:viewControllerAfterViewController:_: methods.

It helps if you subclass UIViewController for your pages to include an index property.

Here’s some sample code to illustrate the object pool:

class PageViewController:UIViewController
{
  var index = 0
}

class MyController:UIPageViewControllerDataSource
{
  var pageViewController:UIPageViewController

  // The object pool:
  var cachedPageViewControllers = [Int:MagicPageViewController]()

  init()
  {
    // Create the UIPageViewController and store it in the pageViewController property, etc.
  }

  // MARK: - UIPageViewControllerDataSource methods

  func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController?
  {
    if let viewController = viewController as? PageViewController
    {
      let currentIndex = viewController.index
      return currentIndex > 0 ? viewControllerForPageAtIndex(currentIndex - 1) : nil
    }
  }

  func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController?
  {
    if let viewController = viewController as? PageViewController
    {
      let currentIndex = viewController.index
      return currentIndex < numberOfPages-1 ? viewControllerForPageAtIndex(currentIndex + 1) : nil
    }
  }
  
  
  func viewControllerForPageAtIndex(index:Int) -> UIViewController
  {
    if let cachedPageViewController = cachedPageViewControllers[index]
    {
      return cachedPageViewController
    }
    
    // Create the page from your storyboard using a naming convention for pages.
    // e.g., Your page view controllers are labelled Page1, …2, etc.
    let viewControllerIdentifier = "Page\(index)"
    guard let pageViewController = self.storyboard.instantiateViewControllerWithIdentifier(viewControllerIdentifier) as? PageViewController else {
      fatalError("Non-PageViewController instance in viewControllerForPageAtIndex. Panic!")
    }
    pageViewController.index = index
    
    cachedPageViewControllers[index] = pageViewController
    
    return pageViewController
  }
}