UIPageViewController transition 'Unbalanced ca

2019-03-11 02:38发布

When I navigate through UIPageViewController faster than its transition animation I am getting 'Unbalanced calls to begin/end appearance transitions for <MyDataViewController>' and one of the two views in landscape isn't shown until I try to turn the page.

Anybody has an idea to solve this bug?

10条回答
孤傲高冷的网名
2楼-- · 2019-03-11 03:14

Make use of your UIPageViewControllerDelegate methods and set up guards to prevent creating new page views when excessive page turns are detected.

  1. You can disable gesture recognizers
  2. Set "userInteraction" to disabled on the UIView
  3. maintain a flag on the UIPageViewController to ignore further input when there is an animation occuring. (warning about this option.. ios5 and ios6 have different ways of determining when the animation started..)
查看更多
萌系小妹纸
3楼-- · 2019-03-11 03:15

I will try to ignore gesture on UIPageViewControllers while transitioning.

查看更多
贪生不怕死
4楼-- · 2019-03-11 03:17

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;
    ...
}
查看更多
手持菜刀,她持情操
5楼-- · 2019-03-11 03:25

Good answer from Basem Saadawy but it has some defect.

Actually the delegate's gestureRecognizerShouldBegin: could be called with no further animation started. This is possible if you start your gesture by vertical finger's moving and its horizontal offset is not enough to start the animation (but is enough to launch gestureRecognizerShouldBegin:). Thus our variable pageAnimationFinished will be set to NO without an actual animation. Therefore the pageViewController: didFinishAnimating: will never be called and you get the current page frozen without a possibility to change it.

That's why a better place to assign NO to this variable is a gesture recognizer's action method with examination of its velocity and translation (we are interested in horizontal direction only).

So the final steps are:

1) Declare an instance variable (a flag):

BOOL pageAnimationFinished;

2) Set its initial value

- (void)viewDidLoad
{
    [super viewDidLoad];
    ...
    pageAnimationFinished = YES;
}

3) Assign a delegate and a custom action to the pan gesture recognizers

for (UIGestureRecognizer * gesRecog in self.pageViewController.gestureRecognizers)
{
    if ([gesRecog isKindOfClass:[UIPanGestureRecognizer class]])
    {
        gesRecog.delegate = self;
        [gr addTarget:self action:@selector(handlePan:)];
    }
}

3') Animation is really started when the gesture's translation is greater in horizontal direction and the finger is moving horizontally at a moment.
I guess the same logic is used in the internal recognizer's action assigned by UIPageViewController.

- (void) handlePan:(UIPanGestureRecognizer *)gestureRecognizer
{
    if (pageAnimationFinished && gestureRecognizer.state == UIGestureRecognizerStateChanged)
    {
        CGPoint vel = [gestureRecognizer velocityInView:self.view];
        CGPoint tr = [gestureRecognizer translationInView:self.view];
        if (ABS(vel.x) > ABS(vel.y) && ABS(tr.x) > ABS(tr.y))
            pageAnimationFinished = NO; // correct place
    }
}

4) Disallowing a gesture if an animation is not finished.

-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
    if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] && ([gestureRecognizer.view isEqual:self.view] || [gestureRecognizer.view isEqual:self.pageViewController.view]))
    {
        UIPanGestureRecognizer * panGes = (UIPanGestureRecognizer *)gestureRecognizer;
        if(!pageAnimationFinished || (currentPage < minimumPage && [panGes velocityInView:self.view].x < 0) || (currentPage > maximumPage && [panGes velocityInView:self.view].x > 0))
            return NO;
    }
    return YES;
}

5) Animation is finished

- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed
{
    pageAnimationFinished = YES;
}

I played too much with it and seems this is a nice solution that works well.

查看更多
登录 后发表回答