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条回答
Anthone
2楼-- · 2019-03-11 03:00

My solution in swift, simple and working:

  1. Set pageviewcontroller delegate to your class
  2. Add below code

    extension MyPageVC: UIPageViewControllerDelegate {
    
        func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
            self.view.isUserInteractionEnabled = false
        }
    
        func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
            self.view.isUserInteractionEnabled = true
        }
    }
    
查看更多
Explosion°爆炸
3楼-- · 2019-03-11 03:01

How about this:

- (void)pageViewController:(UIPageViewController*)pgVC willTransitionToViewControllers:(NSArray*)pendingVCs
{
    pgVC.dataSource = nil; // ... to disallow user to change pages until animation completes
}

- (void)pageViewController:(UIPageViewController*)pgVC
        didFinishAnimating:(BOOL)finished
   previousViewControllers:(NSArray*)prevVCs
       transitionCompleted:(BOOL)completed
{
    if(completed || finished) {
        pgVC.dataSource = _pgViewDataSource; // ... to allow user to change pages again
    }
}
查看更多
劫难
4楼-- · 2019-03-11 03:06

Solved following these steps:
1- Declare a flag to indicate that the animation has finished or not:

BOOL pageAnimationFinished;

2- Set this flag to true in viewDidLoad:

pageAnimationFinished = YES;

3- Disable tapGesture for the pageViewController and assign 'self' to panGestureRecognizer delegate:

for (UIGestureRecognizer * gesRecog in self.pageViewController.gestureRecognizers)
{
    if ([gesRecog isKindOfClass:[UITapGestureRecognizer class]])
        gesRecog.enabled = NO;
    else if ([gesRecog isKindOfClass:[UIPanGestureRecognizer class]])
        gesRecog.delegate = self;
}

4- Allow/Disallow panGestureRecognizer through the following gesture recognizer delegate method:

-(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;
        pageAnimationFinished = NO;
    }
    return YES;
}

5- Add the following pageViewController delegate method:

- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed
{
    pageAnimationFinished = YES;
}
查看更多
Juvenile、少年°
5楼-- · 2019-03-11 03:10

Here's a QUICK version using the delegate:

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.

查看更多
做自己的国王
6楼-- · 2019-03-11 03:11

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!

查看更多
Rolldiameter
7楼-- · 2019-03-11 03:13

I had to add it to viewDidAppear: to make it work

    - (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    self.pageAnimationFinished = YES;
}
查看更多
登录 后发表回答