UIPageViewController手势正在调用viewControllerAfter:但不animation

我有一个非常有趣的UIPageViewController问题。

我的项目设置与示例页面基础应用程序模板非常相似。 每隔一段时间(但在某种程度上可重现)一个特定的平移手势将调用-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController

我返回下一页的视图控制器,但页面翻转animation永远不会运行,我的委托方法永远不会被调用。

这里是viewControllerAfterViewController的代码

 -(UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController { PageDisplayViewController *vc = (PageDisplayViewController *)viewController; NSUInteger index = [self.pageFetchController.fetchedObjects indexOfObject:vc.page]; if(index == (self.pageFetchController.fetchedObjects.count - 1)) return nil; return [self getViewControllerForIndex:(++index)]; } 

这里是getViewControllerForIndex:

 -(PageDisplayViewController *)getViewControllerForIndex:(NSUInteger)index { PageDisplayViewController *newVC = [self.storyboard instantiateViewControllerWithIdentifier:@"PageDisplayController"]; newVC.page = [self.pageFetchController.fetchedObjects objectAtIndex:(index)]; newVC.view.frame = CGRectMake(0, 0, 1024, 604); NSLog(@"%i", index); if(index == 0) { //We're moving to the first, animate the back button to be hidden. [UIView animateWithDuration:0.5 animations:^ { self.backButton.alpha = 0.f; } completion:^(BOOL finished){ self.backButton.hidden = YES; }]; } else if(index == (self.pageFetchController.fetchedObjects.count - 1)) { [UIView animateWithDuration:0.5 animations:^{ self.nextButton.alpha = 0.f; } completion:^(BOOL finished){ self.nextButton.hidden = YES; }]; } else { BOOL eitherIsHidden = self.nextButton.hidden || self.backButton.hidden; if(eitherIsHidden) { [UIView animateWithDuration:0.5 animations:^{ if(self.nextButton.hidden) { self.nextButton.hidden = NO; self.nextButton.alpha = 1.f; } if(self.backButton.hidden) { self.backButton.hidden = NO; self.backButton.alpha = 1.f; } }]; } } return newVC; } 

基本上,我创build视图控制器,设置它的数据对象,然后根据索引淡出下一个/返回button。

委托方法

 -(void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed { PageDisplayViewController *vc = [previousViewControllers lastObject]; NSUInteger index = [self.pageFetchController.fetchedObjects indexOfObject:vc.page]; if (!completed) { [self.pagePreviewView setCurrentIndex:index]; NSLog(@"Animation Did not complete, reverting pagepreview"); } else { PageDisplayViewController *curr = [pageViewController.viewControllers lastObject]; NSUInteger i = [self.pageFetchController.fetchedObjects indexOfObject:curr.page]; [self.pagePreviewView setCurrentIndex:i]; NSLog(@"Animation compeleted, updating pagepreview. Index: %u", i); } } 

我只是注意到这个问题,因为随机,我的后退button会重新出现在屏幕上。 在那里扔了一些NSLog()语句之后,我注意到我的dataSource方法被索引为1,但没有animation播放或委托被调用。 更可怕的是,如果我试图平移下一页,索引1会被再次调用。

我担心这可能是UIPageViewController的一个错误。

请首先看看我的另一个答案,这个答案有严重的缺陷,但是我会把它留在这里,因为它可能还会帮助别人。


首先,免责声明:以下解决scheme是HACK 。 它在我testing的环境中工作,但不能保证它在你的工作,也不会被下一次更新打破。 所以要小心。

TL; DR:抓住UIPanGestureRecognizerUIPageViewController并劫持它的委托调用,但继续转发到原来的目标。

更长的版本:

我在这个问题上的发现:在iOS 6中提供的UIPageViewController在行为上与在iOS 5中的不同之处在于,即使在任何意义上都没有翻页,它可以在其数据源上调用pageViewController:viewControllerBeforeViewController: :没有轻拍,轻扫或有效的方向匹配平移已被识别)。 当然,这打破了我们以前的假设:前/后调用等同于“animation开始”触发器,并始终跟着一个pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:调用委托。 (最终这是一个大胆的假设,但我想我并不孤单。)

我发现当页面视图控制器上的默认UIPanGestureRecognizer开始识别最后与控制器的方向不匹配的平移手势时(例如水平分页中的垂直平移),可能会发生额外的数据源调用PVC)。 有趣的是,在我的环境中,它始终是受到打击的“前”方法,而不是“之后”。 其他人build议干扰手势识别器的代表,但这并不适用于我所描述的方式,所以我一直在试验。

最后我find了一个解决方法。 首先我们抓取页面视图控制器的平移手势识别器:

 @interface MyClass () <UIGestureRecognizerDelegate> { UIPanGestureRecognizer* pvcPanGestureRecognizer; id<UIGestureRecognizerDelegate> pvcPanGestureRecognizerDelegate; } ... for ( UIGestureRecognizer* recognizer in pageViewController.gestureRecognizers ) { if ( [recognizer isKindOfClass:[UIPanGestureRecognizer class]] ) { pvcPanGestureRecognizer = (UIPanGestureRecognizer*)recognizer; pvcPanGestureRecognizerDelegate = pvcPanGestureRecognizer.delegate; pvcPanGestureRecognizer.delegate = self; break; } } 

然后我们在我们的类中实现UIGestureRecognizerDelegate协议:

 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { if ( gestureRecognizer == pvcPanGestureRecognizer && [pvcPanGestureRecognizerDelegate respondsToSelector:@selector(gestureRecognizer:shouldReceiveTouch:)] ) { return [pvcPanGestureRecognizerDelegate gestureRecognizer:gestureRecognizer shouldReceiveTouch:touch]; } return YES; } - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { if ( gestureRecognizer == pvcPanGestureRecognizer && [pvcPanGestureRecognizerDelegate respondsToSelector:@selector(gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:)] ) { return [pvcPanGestureRecognizerDelegate gestureRecognizer:gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:otherGestureRecognizer]; } return NO; } - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { if ( gestureRecognizer == pvcPanGestureRecognizer && [pvcPanGestureRecognizerDelegate respondsToSelector:@selector(gestureRecognizerShouldBegin:)] ) { return [pvcPanGestureRecognizerDelegate gestureRecognizerShouldBegin:gestureRecognizer]; } return YES; } 

显然,这些方法并没有做任何明智的事情,他们只是将调用转发给原来的委托(确保实际执行它们)。 尽pipe如此,这个转发似乎足以让PVC行为,而不需要时不调用数据源。

此解决方法解决了运行iOS 6的设备上的问题。使用iOS 6 SDK编译但使用iOS 5的部署目标的代码已经在5.x设备上完美运行,因此不需要修复我的testing也没有任何伤害。

希望有人认为这有用。

由于我在第一个答案中仍然收到神秘的崩溃,所以我一直在寻找一个“足够好”的解决scheme,这个解决scheme更less依赖于关于页面视图控制器(PVC)基本行为的个人假设。 这是我设法提出的。

我以前的做法是侵入性的,比可接受的解决scheme更具有解决方法。 而不是打击PVC迫使它做我认为应该做的事情,似乎最好接受这样的事实:

  • pageViewController:viewControllerBeforeViewController:pageViewController:viewControllerAfterViewController:方法可以被UIKit调用任意次数,并且
  • 绝对不能保证这些对应于分页animation,也不能保证对pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:的调用pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:

这意味着我们不能使用前/后方法作为“animation开始”(但请注意, didFinishAnimating仍然用作“animation结束”事件)。 那么我们怎么知道一个animation的确开始了呢?

根据我们的需要,我们可能会对以下事件感兴趣:

  1. 用户开始摆弄页面:一个很好的指标是前/后callback,或者更确切地说是第一个callback。

  2. 翻页手势的第一视觉反馈:我们可以使用KVO对PVC的轻拍和平移手势识别器的state属性。 当观察到一个UIGestureRecognizerStateBegan值用于平移时,我们可以确信视觉反馈将会随之而来。

  3. 用户完成通过释放触摸拖动页面:再次,KVO。 当UIGestureRecognizerStateRecognized值被报告用于平移或敲击时,当PVC实际上将转动页面时,所以这可以被用作“animation开始”。

  4. UIKit启动分页animation:我不知道如何得到这个直接的反馈。

  5. UIKit总结了分页animation:一块蛋糕,只听pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:

对于KVO,只需抓住PVC的手势识别器如下:

 @interface MyClass () <UIGestureRecognizerDelegate> { UIPanGestureRecognizer* pvcPanGestureRecognizer; UITapGestureRecognizer* pvcTapGestureRecognizer; } ... for ( UIGestureRecognizer* recognizer in pageViewController.gestureRecognizers ) { if ( [recognizer isKindOfClass:[UIPanGestureRecognizer class]] ) { pvcPanGestureRecognizer = (UIPanGestureRecognizer*)recognizer; } else if ( [recognizer isKindOfClass:[UITapGestureRecognizer class]] ) { pvcTapGestureRecognizer = (UITapGestureRecognizer*)recognizer; } } 

然后注册你的课程作为state属性的观察者:

 [pvcPanGestureRecognizer addObserver:self forKeyPath:@"state" options:NSKeyValueObservingOptionNew context:NULL]; [pvcTapGestureRecognizer addObserver:self forKeyPath:@"state" options:NSKeyValueObservingOptionNew context:NULL]; 

并执行通常的callback:

 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ( [keyPath isEqualToString:@"state"] && (object == pvcPanGestureRecognizer || object == pvcTapGestureRecognizer) ) { UIGestureRecognizerState state = [[change objectForKey:NSKeyValueChangeNewKey] intValue]; switch (state) { case UIGestureRecognizerStateBegan: // trigger for visual feedback break; case UIGestureRecognizerStateRecognized: // trigger for animation-begin break; // ... } } } 

当你完成后,不要忘了取消订阅这些通知,否则你可能会在你的应用程序中遇到泄漏和奇怪的崩溃:

 [pvcPanGestureRecognizer removeObserver:self forKeyPath:@"state"]; [pvcTapGestureRecognizer removeObserver:self forKeyPath:@"state"]; 

这就是所有人!

我已经尝试过你的解决scheme,它几乎工作,但仍然有一些问题。 最好的解决scheme来与添加方法

  - (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray *)pendingViewControllers 

这是从iOS 6开始,它是必需的。 如果不实施,这些手势可能会出现问题。 实施它有助于解决大部分问题。

尝试这个…

  - (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController { for (UIGestureRecognizer *gr in pageViewController.gestureRecognizers) { if([gr isKindOfClass:[UIPanGestureRecognizer class]]) { UIPanGestureRecognizer *pgr = (UIPanGestureRecognizer*)gr; CGPoint velocity = [pgr velocityInView:pageViewController.view]; BOOL verticalSwipe = fabs(velocity.y) > fabs(velocity.x); if(verticalSwipe) return nil; } } .... }