UIPageViewController gesture is calling viewControllerAfter: but doesn't animate
我的UIPageViewController有一个非常有趣的问题。
我的项目的设置与示例基于页面的应用程序模板非常相似。
时不时地(但在一定程度上可再现)某个平移手势会呼出
我返回下一页的viewcontroller,但从未运行过页面翻转动画,也从未调用过我的委托方法。
这是
1 2 3 4 5 6 7 | -(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)]; } |
这是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | -(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; } |
基本上,我创建视图控制器,设置它的数据对象,然后根据索引淡出下一个/后退按钮。
委托方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | -(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); } } |
我仅注意到此问题,因为随机地,我的后退按钮会重新出现在屏幕上。在其中放入一些
我担心这可能是UIPageViewController的错误。
由于我的第一个答案仍在实现中遇到神秘的崩溃,因此我一直在寻找"足够好"的解决方案,该解决方案较少依赖于有关页面视图控制器(PVC)基本行为的个人假设。这是我设法提出的。
我以前的方法是一种侵入性方法,而不是可接受的解决方案,更多的是替代方法。与其与PVC进行抗争以强迫它执行我认为应该做的事情,不如更好地接受以下事实:
-
UIKit可以多次调用
pageViewController:viewControllerBeforeViewController: 和pageViewController:viewControllerAfterViewController: 方法,并且 -
绝对不能保证其中任何一个都与分页动画相对应,也不能保证在它们之后都调用
pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:
这意味着我们不能将之前/之后的方法用作"动画开始"(但是,请注意,
根据我们的需求,我们可能对以下事件感兴趣:
用户开始摆弄页面:
一个很好的指标是之前/之后的回调,或更准确地说是第一个。
页面翻动手势的第一视觉反馈:
我们可以在PVC的点击和平移手势识别器的
用户通过释放触摸完成拖动页面:
再次,KVO。当报告
UIKit启动分页动画:
我不知道该如何获得直接反馈。
UIKit结束了分页动画:
小菜一碟,只听
对于KVO,只需抓住PVC的手势识别器,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | @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; } } |
然后将您的类注册为
1 2 3 4 5 6 7 8 | [pvcPanGestureRecognizer addObserver:self forKeyPath:@"state" options:NSKeyValueObservingOptionNew context:NULL]; [pvcTapGestureRecognizer addObserver:self forKeyPath:@"state" options:NSKeyValueObservingOptionNew context:NULL]; |
并实现通常的回调:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | - (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; // ... } } } |
完成后,请不要忘记取消订阅这些通知,否则您的应用程序可能会泄漏并发生奇怪的崩溃:
1 2 3 4 | [pvcPanGestureRecognizer removeObserver:self forKeyPath:@"state"]; [pvcTapGestureRecognizer removeObserver:self forKeyPath:@"state"]; |
那就是所有人!
我已经尝试过您的解决方案,它几乎可以正常工作,但是仍然存在一些问题。最好的解决方案是添加方法
1 | - (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray *)pendingViewControllers |
从iOS 6开始可用,并且是必需的。如果不执行它,这些手势可能会出现问题。实施它有助于解决大部分问题。
请首先查看我的其他答案,这个答案有严重的缺陷,但我将其留在此处,因为它可能仍会帮助某人。
首先,免责声明:以下解决方案是HACK。它确实可以在我测试过的环境中工作,但不能保证它可以在您的环境中工作,也不能保证下次更新不会破坏它。因此,请谨慎操作。
TL; DR:获取
较长版本:
我在以下问题上的发现:iOS 6中随附的
我发现,当页面视图控制器上的默认
终于我找到了解决方法。首先,我们获取页面视图控制器的平移手势识别器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | @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; } } |
然后,在我们的类中实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | - (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; } |
显然,这些方法没有做任何明智的事情,它们只是将调用转发给原始委托人(确保实际上实现了它们)。尽管如此,这种转发似乎足以使PVC表现出来,并且在不需要时不调用数据源。
此变通办法为我解决了在运行iOS 6的设备上的问题。使用iOS 6 SDK编译但部署目标为iOS 5的代码已经在5.x设备上完美运行,因此不需要在此进行修复,但根据我的测试也没有任何伤害。
希望有人觉得这有用。
试试这个...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | - (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; } } .... } |