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:抓住UIPanGestureRecognizer
的UIPageViewController
并劫持它的委托调用,但继续转发到原来的目标。
更长的版本:
我在这个问题上的发现:在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的确开始了呢?
根据我们的需要,我们可能会对以下事件感兴趣:
-
用户开始摆弄页面:一个很好的指标是前/后callback,或者更确切地说是第一个callback。
-
翻页手势的第一视觉反馈:我们可以使用KVO对PVC的轻拍和平移手势识别器的
state
属性。 当观察到一个UIGestureRecognizerStateBegan
值用于平移时,我们可以确信视觉反馈将会随之而来。 -
用户完成通过释放触摸拖动页面:再次,KVO。 当
UIGestureRecognizerStateRecognized
值被报告用于平移或敲击时,当PVC实际上将转动页面时,所以这可以被用作“animation开始”。 -
UIKit启动分页animation:我不知道如何得到这个直接的反馈。
-
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; } } .... }
- 升级到Swift 3后,属性’self.delegate’未在super.init调用时初始化
- 呈现视图控制器模式重置当前视图
- 如何selecttabBar项目,以模态方式显示viewController
- iOS上的Dropbox应用程序是否具有URLscheme?
- URLForUbiquityContainerIdentifier是否应该在主线程之外的线程中调用?
- ALAssetsLibrary从自定义相册ios中获取图像
- Firebase queryOrderedByChild()方法不给出sorting数据
- 在iMessage应用程序中检查风景/纵向(扩展名)
- NSLD在LLDB中没有输出。 在GDB工作