我对UIPageViewController有一个非常有趣的问题。
我的项目设置与示例基于页面的应用程序模板非常相似。
偶尔(但在某种程度上可重现)某个平移手势将呼叫-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
。
我返回下一页的viewcontroller,但是从不运行页面翻转动画,我的委托方法永远不会被调用。
以下是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;
}
基本上,我创建视图控制器,设置它的数据对象,然后根据索引淡出下一个/后退按钮。
委托方法
-(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);
}
}
我只注意到这个问题因为随机,我的后退按钮会重新出现在屏幕上。在那里抛出一些NSLog()
语句之后,我注意到我的dataSource方法被调用索引为1,但是没有动画播放或委托被调用。甚至更可怕的是,如果我尝试平移下一页,则索引1将被调用为AGAIN。
我担心这可能是UIPageViewController的一个错误。
答案 0 :(得分:8)
由于我在第一个回答中仍然收到了实施的神秘崩溃,我一直在寻找一个“足够好”的解决方案,这个解决方案较少依赖于有关页面视图控制器(PVC)基础行为的个人假设。以下是我设法提出的内容。
我以前的方法有点干扰,而且更像是一种解决方案,而不是一种可接受的解决方案。而不是打击PVC以迫使它做我认为应该做的事情,似乎更好地接受以下事实:
pageViewController:viewControllerBeforeViewController:
和pageViewController:viewControllerAfterViewController:
方法任意次数,pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:
的调用这意味着我们不能将before / after方法用作“animation-begin”(但请注意,didFinishAnimating
仍然用作“动画结束”事件)。那么我们怎么知道动画确实已经开始了?
根据我们的需求,我们可能会对以下事件感兴趣:
用户开始摆弄页面: 一个很好的指标是回调之前/之后,或者更确切地说是第一回调。
翻页手势的第一次视觉反馈:
我们可以在点击的state
属性上使用KVO并平移PVC的手势识别器。当观察到平移的UIGestureRecognizerStateBegan
值时,我们可以非常肯定会看到视觉反馈。
用户通过释放触摸完成拖动页面:
再次,KVO。当报告UIGestureRecognizerStateRecognized
值进行平移或点击时,就是当PVC实际上要翻页时,这可以用作“动画开始”。
UIKit启动分页动画: 我不知道如何得到这方面的直接反馈。
UIKit总结了分页动画:
小蛋糕,只听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];
实现通常的回调:
- (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"];
这就是所有人!
答案 1 :(得分:4)
首先请看我的另一个答案,这个有严重的缺陷,但我将它留在这里,因为它可能仍然有助于某人。
首先,免责声明:以下解决方案是 HACK 。它确实在我测试的环境中工作,但不保证它在你的环境中工作,也不会在下次更新时被破坏。所以要小心。
TL; DR:抓住UIPanGestureRecognizer
的{{1}}并劫持其委托电话但继续转发给原始目标。
更长的版本:
我对此问题的调查结果:iOS 6中提供的UIPageViewController
行为与iOS 5中的行为不同,即使没有翻页,它也可能会调用其数据源上的UIPageViewController
在任何意义上继续(阅读:没有点击,滑动或有效的方向匹配平移已被识别)。当然,这打破了我们之前的假设,即前/后调用等同于“动画开始”触发器,并始终跟随对代理的pageViewController:viewControllerBeforeViewController:
调用。 (最终这是一个大胆的假设,但我想我并不孤单。)
我发现当页面视图控制器上的默认pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:
开始识别最终与控制器方向不匹配的平移手势时,可能会发生对数据源的额外调用(例如,在水平寻呼PVC中的垂直平移。有趣的是,在我的环境中,它始终是受到打击的“之前”方法,而不是“之后”。 Others suggested干扰了手势识别器的代表,但这对我的描述方式不起作用,所以我一直在试验。
最后我找到了解决方法。首先,我们抓住页面视图控制器的平移手势识别器:
UIPanGestureRecognizer
然后我们在课堂上实现@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
显然,这些方法没有做任何明智的事情,他们只是将调用转发给原始委托(确保实际实现它们)。尽管如此,这种转发似乎足以让PVC表现出来并且在没有必要时不会调用数据源。
此解决方法为我在运行iOS 6的设备上解决了这个问题。使用iOS 6 SDK编译但部署目标为iOS 5的代码已经在5.x设备上运行完美,因此无需修复但根据我的测试,它也没有任何伤害。
希望有人觉得这很有用。
答案 2 :(得分:4)
我已经尝试过您的解决方案,它几乎正常运行,但仍有一些问题。最好的解决方案是添加方法
- (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray *)pendingViewControllers
从iOS 6开始提供,必需。如果不实现它,那些手势可能会出现问题。实施它有助于解决大部分问题。
答案 3 :(得分:0)
试试这个......
- (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;
}
}
....
}