hirsch
hirsch

Reputation: 103

Swift - UIPageViewController always repeats second ViewController

Setup: I'm trying to create a ViewController with an embedded UIPageViewController with 4 pages that are each their own ViewController.

Problem: When scrolling the second view is always repeated. For example with View1, View2, View3, View4 scrolling reveals View1, View2, View2, View3, View4 then scrolling back has this order View4, View3, View3, View2, View1 even though the page control is accurate.

I've look at many tutorials online including answers here and here.

Here is my page view controller class:

import UIKit

class HostGamePageViewController: ROLViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {

  // Constants
  let ViewControllers: NSArray = ["HostGameTest1", "HostGameTest2", "HostGameTest3", "HostGameTest4"]

  // Properties
  var pageViewController: UIPageViewController!
  var currentViewController: String!

  // UI
  @IBOutlet var pageControl: UIPageControl!

  override func viewDidLoad() {
    super.viewDidLoad()

    pageViewController = storyboard?.instantiateViewControllerWithIdentifier("HGPageViewController") as? UIPageViewController
    pageViewController.delegate = self
    pageViewController.dataSource = self

    let startingViewController = viewControllerAtIndex(0)!
    pageViewController.setViewControllers([startingViewController], direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: {done in })

    addChildViewController(pageViewController)
    view.addSubview(pageViewController.view)
    pageViewController.view.frame = view.bounds
    pageViewController.didMoveToParentViewController(self)
    view.gestureRecognizers = pageViewController.gestureRecognizers

    // Setup page controls
    pageControl.numberOfPages = ViewControllers.count
    view.bringSubviewToFront(pageControl)
  }

  override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
  }

  // Get the index of the current page view
  func indexOfViewController() -> Int {
    if currentViewController != nil {
      return ViewControllers.indexOfObject(currentViewController)
    }

    return NSNotFound
  }

  // Get the view controller of the given index
  func viewControllerAtIndex(index: Int) -> UIViewController? {
    if ViewControllers.count == 0 || index >= ViewControllers.count {
      return nil
    }

    pageControl.currentPage = index
    currentViewController = ViewControllers[index] as String

    return storyboard?.instantiateViewControllerWithIdentifier(currentViewController) as? ROLViewController
  }

  // Handle clicks on the page control
  @IBAction func pageControlClick(sender: UIPageControl) {
    var direction: UIPageViewControllerNavigationDirection

    if sender.currentPage < indexOfViewController() {
      direction = UIPageViewControllerNavigationDirection.Forward
    } else {
      direction = UIPageViewControllerNavigationDirection.Reverse
    }

    if let viewController = viewControllerAtIndex(sender.currentPage) {
      pageViewController.setViewControllers([viewController], direction: direction, animated: false, completion: nil)
    }
  }

  func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
    var index = indexOfViewController()

    if index == 0 || index == NSNotFound {
      return nil
    }

    index--
    return viewControllerAtIndex(index)
  }

  func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
    var index = indexOfViewController()

    if index == NSNotFound {
      return nil
    }

    index++
    if index == ViewControllers.count {
      return nil
    }

    return viewControllerAtIndex(index)
  }

}

and here is a link to a screenshot of my storyboard: http://f.cl.ly/items/0V3W133Q34170q39393Q/Image%202014-09-11%20at%2011.49.44%20AM.png

Am I setting up the dateSource incorrectly? I read somewhere that UIPageViewController does some caching by turning animation to false which is the supposed fix, didn't do anything for me. Any help would be much appreciated. Thanks!

Upvotes: 4

Views: 4519

Answers (1)

Fdo
Fdo

Reputation: 1093

I've had a very similar problem (not using swift, in obj-c, had 3 view controllers and the one in the middle repeated one time: "0,1,1,2"). Turned out it was all about the Page View Controller Data Source methods...

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController;
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController;

I haven't really digged into swift (nor had the interest in doing so yet), but pseudo-reading your code leaves me the idea that you restrict the limits of the "before" and "after" key methods (swift-wise) with variables contained inside the controller used as the UIPageViewControllerDataSource, that is exactly what I changed and fixed the issue.

I don't have the best english but I hope you understood that last statement. The fix I used was based on this tutorial. In my case, the view controllers I used in all 3 cases where "UINavigationViewControllers", so I created a "NavigationWrapperViewController" that had a property:

@property(assign, nonatomic) NSUInteger index;

Then, when checking for the boundary limits in the key Data Source methods did (only showing one, use the same technique for the other one):

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
   NSUInteger indice = ((NavigationWrapperViewController*)viewController).index;
   if ((indice == 0) || (indice == NSNotFound)) {
       return nil;
   }    
   return [self viewControllerAtIndex:(--indice)];
}

And when instantiating view controllers in viewControllerAtIndex, don't forget to set that property correctly before returning them:

NavigationWrapperViewController *wrapperViewController = [self.storyboard instantiateViewControllerWithIdentifier:self.ViewControllers[index]];
wrapperViewController.index = index;

return wrapperViewController;

The subtle difference is that the index of the view controller displayed is stored by the current view controller (using a simple wrapper, that class was made entirely with that purpose, it just has a property implementation in his .h file) instead of the UIPageViewControllerDataSource responsible class.

That fixed my problem, maybe it was some sort of thread related issue that influenced on the current view controller index that was fetched and that caused the whole - repeating view controllers - problem.

Hope it helps if you are willing to try it out!

Upvotes: 5

Related Questions