The problem:
I have a master UIPageViewController
(MainPageVC
) with three imbedded page views (A
, B
, & C
) that are accessible both with swipe gestures and by pressing the appropriate locations in a custom page indicator* in the MainPageVC
(*not a true UIPageControl
but comprised of three ToggleButton
s - a simple reimplementation of UIButton
to become a toggle-button). My setup is as follows:
Previous reading:
Reliable way to track Page Index in a UIPageViewController - Swift, A reliable way to get UIPageViewController current index, and UIPageViewController: return the current visible view
indicated that the best way to do this was with didFinishAnimating
calls, and manually keep track of the current page index, but I'm finding that this does not deal with certain edge cases.
I have been trying to produce a safe way of keeping track of the current page index (with didFinishAnimating
and willTransitionTo
methods) but am having trouble with the edge case where a user is in view A
, and then swipes all the way across to C
(without lifting up their finger), and then beyond C
, and then releasing their finger... in this instance didFinishAnimating
isn't called and the app still believes it is in A
(i.e. A
toggle button is still pressed and pageIndex is not updated correctly by the viewControllerBefore
and viewControllerAfter
methods).
My 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) -> UIViewController? {
let onboardingViewControllerClass = String(describing: viewController.classForCoder)
let viewControllerIndex = viewControllerNames.index(of: onboardingViewControllerClass)
let newViewControllerIndex = viewControllerIndex! - 1
if(newViewControllerIndex < 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) -> UIViewController? {
let onboardingViewControllerClass = String(describing: viewController.classForCoder)
let viewControllerIndex = viewControllerNames.index(of: onboardingViewControllerClass)
let newViewControllerIndex = viewControllerIndex! + 1
if(newViewControllerIndex > 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
}
}
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 C
that should otherwise be guaranteed to exist, and an unexpected nil
or indexOutOfBounds
error is thrown.