When I navigate through UIPageViewController
faster than its transition animation I am getting \'Unbalanced calls to begin/end appearance transitions for <
add this code (make sure you're including the UIPageViewControllerDelegate in your header or class extension, and assign self.pageViewController.delegate = self;
):
- (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray *)pendingViewControllers {
self.pageAnimationFinished = NO;
}
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed {
self.pageAnimationFinished = YES;
}
then check self.pageAnimationFinished
and return nil if it's == NO
.
Longer Explanation:
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed
We can use this delegate method from UIPageViewControllerDelegate
to know when the animation from flipping or swiping through pages finishes. Using this we just can implement it like this:
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed {
pageAnimationFinished = YES;
}
then, just return nil
in your
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(PageViewController *)viewController
and
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(PageViewController *)viewController
when
pageAnimationFinished == NO
. Be sure to set pageAnimationFinished
to NO
when you animate. The best way to know when you animate is by using the opposite of
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed
namely:
- (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray *)pendingViewControllers
I haven't seen that warning ever since and this can be done in 1/3 of the lines as the other solutions. And it's MUCH easier to follow.
The above answers were right, but I think more elaborate than needed, and cookbook is helpful. So here is what seems to be working for me:
In the view controller that sets up and calls the pageViewController, declare:
@property (assign) BOOL pageIsAnimating;
and in viewDidLoad:
pageIsAnimating = NO;
add this:
- (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray *)pendingViewControllers {
pageIsAnimating = YES;
}
and add a couple of lines to:
- (void)pageViewController:(UIPageViewController *)pageViewController
didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers
transitionCompleted:(BOOL)completed {
if (completed || finished) // Turn is either finished or aborted
pageIsAnimating = NO;
...
}
The gestures are suppressed by declining to provide view controller information:
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
viewControllerAfterViewController:(UIViewController *)viewController {
if (pageIsAnimating)
return nil;
...
return after;
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
viewControllerBeforeViewController:(UIViewController *)viewController {
if (pageIsAnimating)
return nil;
...
return before;
}
Oh, and orientation changes reset the flag:
- (UIPageViewControllerSpineLocation)pageViewController:(UIPageViewController *)pageViewController
spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation {
pageIsAnimating = NO;
...
}
Here's the Swift version of Bill Cheswick's answer (currently the top answer):
Add a variable to hold the current state:
var pageIsAnimating = false
Set the animating state:
func pageViewController(pageViewController: UIPageViewController, willTransitionToViewControllers pendingViewControllers: [UIViewController]) {
self.pageIsAnimating = true
}
func pageViewController(pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if finished || completed {
self.pageIsAnimating = false
}
}
Block the transitions if it's currently animating:
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
if self.pageIsAnimating {
return nil
}
// Your code here
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
if self.pageIsAnimating {
return nil
}
// Your code here
}
Thank you Bill Cheswick!
Make use of your UIPageViewControllerDelegate methods and set up guards to prevent creating new page views when excessive page turns are detected.