UIPageViewController手势正在调用viewControllerAfter:但是没有动画

时间:2012-09-21 15:08:44

标签: ios uipageviewcontroller

我对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的一个错误。

4 个答案:

答案 0 :(得分:8)

由于我在第一个回答中仍然收到了实施的神秘崩溃,我一直在寻找一个“足够好”的解决方案,这个解决方案较少依赖于有关页面视图控制器(PVC)基础行为的个人假设。以下是我设法提出的内容。

我以前的方法有点干扰,而且更像是一种解决方案,而不是一种可接受的解决方案。而不是打击PVC以迫使它做我认为应该做的事情,似乎更好地接受以下事实:

  • UIKit可以调用pageViewController:viewControllerBeforeViewController:pageViewController:viewControllerAfterViewController:方法任意次数,
  • 绝对不能保证其中任何一个都与分页动画相对应,也不能保证它们会跟随pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:的调用

这意味着我们不能将before / after方法用作“animation-begin”(但请注意,didFinishAnimating仍然用作“动画结束”事件)。那么我们怎么知道动画确实已经开始了?

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

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

  2. 翻页手势的第一次视觉反馈: 我们可以在点击的state属性上使用KVO并平移PVC的手势识别器。当观察到平移的UIGestureRecognizerStateBegan值时,我们可以非常肯定会看到视觉反馈。

  3. 用户通过释放触摸完成拖动页面: 再次,KVO。当报告UIGestureRecognizerStateRecognized值进行平移或点击时,就是当PVC实际上要翻页时,这可以用作“动画开始”。

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

  5. UIKit总结了分页动画: 小蛋糕,只听pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:

  6. 对于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;
                }
            }
    ....
}