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

Dav*_*Dev 8 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)];
}
Run Code Online (Sandbox Code Playgroud)

这里是 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;
}
Run Code Online (Sandbox Code Playgroud)

基本上,我创建视图控制器,设置它的数据对象,然后根据索引淡出下一个/后退按钮.

委托方法

-(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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我只注意到这个问题因为随机,我的后退按钮会重新出现在屏幕上.在抛出一些NSLog()语句之后,我注意到我的dataSource方法被调用索引为1,但是没有动画播放或者委托被调用.甚至更可怕的是,如果我试图平移下一页,索引1将被调用为AGAIN.

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

Lvs*_*sti 8

由于我在第一个答案中仍然收到了实施的神秘崩溃,我一直在寻找一个"足够好"的解决方案,这个解决方案更少依赖于关于页面视图控制器(PVC)底层行为的个人假设.这是我设法提出的.

我以前的方法是一种侵入性的,而不是一种可接受的解决方案.而不是打击PVC以迫使它做我认为应该做的事情,似乎最好接受以下事实:

  • pageViewController:viewControllerBeforeViewController:pageViewController:viewControllerAfterViewController:方法可以被称为的由UIKit的任意次数,并
  • 绝对不能保证这些中的任何一个都对应于分页动画,也不会保证它们之后会被调用 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:.

对于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;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后将您的类注册为state属性的观察者:

[pvcPanGestureRecognizer addObserver:self
                          forKeyPath:@"state"
                             options:NSKeyValueObservingOptionNew
                             context:NULL];
[pvcTapGestureRecognizer addObserver:self
                          forKeyPath:@"state"
                             options:NSKeyValueObservingOptionNew
                             context:NULL];
Run Code Online (Sandbox Code Playgroud)

并实现通常的回调:

- (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;

            // ...
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

完成后,不要忘记取消订阅这些通知,否则您的应用中可能会出现泄漏和奇怪的崩溃:

[pvcPanGestureRecognizer removeObserver:self
                             forKeyPath:@"state"];
[pvcTapGestureRecognizer removeObserver:self
                             forKeyPath:@"state"];
Run Code Online (Sandbox Code Playgroud)

这就是所有人!


Lvs*_*sti 4

请首先看看我的其他答案,这个答案有严重的缺陷,但我将其留在这里,因为它可能仍然对某人有帮助。


首先,免责声明:以下解决方案是HACK。它确实在我测试的环境中工作,但不能保证它在您的环境中工作,也不能保证它不会被下一次更新破坏。所以要小心行事。

TL;DR:抓住UIPanGestureRecognizerUIPageViewController劫持其委托呼叫,但继续将它们转发到原始目标。

更长的版本:

我对这个问题的发现:UIPageViewControlleriOS 6 中的行为与 iOS 5 中的行为不同,即使pageViewController:viewControllerBeforeViewController:没有任何意义上的翻页,它也可能会调用其数据源(阅读:没有点击,滑动,或者已识别出有效的方向匹配平移)。当然,这打破了我们之前的假设,即之前/之后的调用相当于“动画开始”触发器,并且始终跟随对pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:委托的调用。(最终这是一个大胆的假设,但我想我并不孤单。)

我发现,当UIPanGestureRecognizer页面视图控制器上的默认设置开始识别最终与控制器方向不匹配的平移手势时,可能会发生对数据源的额外调用(例如,水平分页 PVC 中的垂直平移) )。有趣的是,在我的环境中,受到影响的总是“之前”的方法,而不是“之后”的方法。其他人建议干扰手势识别器的代表,但这对我来说并不像那里描述的那样有效,所以我继续进行实验。

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

@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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

显然,这些方法没有做任何明智的事情,它们只是将调用转发给原始委托(确保实际实现它们)。尽管如此,这种转发似乎足以让 PVC 正常工作,并且在不需要时不会调用数据源。

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

希望有人觉得这很有用。