title: Reliably track Page Index in a UIPageViewController (Swift)
body: <p>The problem:</p>
  
  <p>I have a master <code>UIPageViewController</code> (<code>MainPageVC</code>) with three imbedded page views (<code>A</code>, <code>B</code>, &amp; <code>C</code>) that are accessible both with swipe gestures and by pressing the appropriate locations in a custom page indicator* in the <code>MainPageVC</code> (*not a true <code>UIPageControl</code> but comprised of three <code>ToggleButton</code>s - a simple reimplementation of <code>UIButton</code> to become a toggle-button). My setup is as follows:</p>
  
  <p><a href="https://i.stack.imgur.com/ClfJI.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/ClfJI.png" alt="Schematic of my view hierarchy"></a></p>
  
  <p>Previous reading:
  <a href="https://stackoverflow.com/questions/38610670/reliable-way-to-track-page-index-in-a-uipageviewcontroller-swift">Reliable way to track Page Index in a UIPageViewController - Swift</a>, <a href="https://stackoverflow.com/questions/36857455/a-reliable-way-to-get-uipageviewcontroller-current-index">A reliable way to get UIPageViewController current index</a>, and <a href="https://stackoverflow.com/questions/8400870/uipageviewcontroller-return-the-current-visible-view/33818615#33818615">UIPageViewController: return the current visible view</a>
   indicated that the best way to do this was with <code>didFinishAnimating</code> calls, and manually keep track of the current page index, but I'm finding that this does not deal with certain edge cases.</p>
  
  <p>I have been trying to produce a safe way of keeping track of the current page index (with <code>didFinishAnimating</code> and <code>willTransitionTo</code> methods) but am having trouble with the edge case where a user is in view <code>A</code>, and then swipes all the way across to <code>C</code> (without lifting up their finger), and then beyond <code>C</code>, and then releasing their finger... in this instance <code>didFinishAnimating</code> isn't called and the app still believes it is in <code>A</code> (i.e. <code>A</code> toggle button is still pressed and pageIndex is not updated correctly by the <code>viewControllerBefore</code> and <code>viewControllerAfter</code> methods).</p>
  
  <p>My code:</p>
  
  <pre><code>@IBOutlet weak var pagerView: UIView!
  @IBOutlet weak var aButton: ToggleButton!
  @IBOutlet weak var bButton: ToggleButton!
  @IBOutlet weak var cButton: ToggleButton!
  
  let viewControllerNames = ["aVC", "bVC", "cVC"]
  lazy var buttonsArray = {
      [aButton, bButton, cButton]
  }()
  var previousPage = "aVC"
  
  var pageVC: UIPageViewController?
  
  func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
      print("TESTING - will transition to")
  
      let currentViewControllerClass = String(describing: pageViewController.viewControllers![0].classForCoder);
      let viewControllerIndex = viewControllerNames.index(of: currentViewControllerClass);
  
      if currentViewControllerClass == previousPage {
          return
      }
  
      let pastIndex = viewControllerNames.index(of: previousPage)
      if buttonsArray[pastIndex!]?.isOn == true {
          buttonsArray[pastIndex!]?.buttonPressed()
      }
  
      if let newPageButton = buttonsArray[viewControllerIndex!] {
          newPageButton.buttonPressed()
      }
  
      self.previousPage = currentViewControllerClass
  }
  
  func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
      print("TESTING - did finish animating")
  
      let currentViewControllerClass = String(describing: pageViewController.viewControllers![0].classForCoder)
      let viewControllerIndex = viewControllerNames.index(of: currentViewControllerClass)
  
      if currentViewControllerClass == previousPage {
          return
      }
  
      let pastIndex = viewControllerNames.index(of: previousPage)
      if buttonsArray[pastIndex!]?.isOn == true {
          buttonsArray[pastIndex!]?.buttonPressed()
      }
  
      if let newPageButton = buttonsArray[viewControllerIndex!] {
          newPageButton.buttonPressed()
      }
  
      self.previousPage = currentViewControllerClass
  }
  
  func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -&gt; UIViewController? {
      let onboardingViewControllerClass = String(describing: viewController.classForCoder)
      let viewControllerIndex = viewControllerNames.index(of: onboardingViewControllerClass)
      let newViewControllerIndex = viewControllerIndex! - 1
      if(newViewControllerIndex &lt; 0) {
          return nil
      } else {
          let storyboard = UIStoryboard(name: "Main", bundle: nil)
          let vc = storyboard.instantiateViewController(withIdentifier: viewControllerNames[newViewControllerIndex])
          if let vc = vc as? BaseTabVC {
              vc.mainPageVC = self
              vc.intendedCollectionViewHeight = pagerViewHeight
          }
          return vc
      }
  }
  
  func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -&gt; UIViewController? {
      let onboardingViewControllerClass = String(describing: viewController.classForCoder)
      let viewControllerIndex = viewControllerNames.index(of: onboardingViewControllerClass)
      let newViewControllerIndex = viewControllerIndex! + 1
      if(newViewControllerIndex &gt; viewControllerNames.count - 1) {
          return nil
      } else {
          let storyboard = UIStoryboard(name: "Main", bundle: nil)
          let vc = storyboard.instantiateViewController(withIdentifier: viewControllerNames[newViewControllerIndex])
          if let vc = vc as? BaseTabVC {
              vc.mainPageVC = self
              vc.intendedCollectionViewHeight = pagerViewHeight
          }
          return vc
      }
  }
  </code></pre>
  
  <p>I'm at a loss as to how to deal with this edge case, the problem is that it can lead to fatal crashes of the app if the user then tries to press something in <code>C</code> that should otherwise be guaranteed to exist, and an unexpected <code>nil</code> or <code>indexOutOfBounds</code> error is thrown.</p>