Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UIPageViewController in iOS6

In iOS6 in the methods viewControllerAfterViewController and viewControllerBeforeViewController if I return nil (for block the page navigation when I am in the first or last page) the app crash with this exception:

'The number of view controllers provided (0) doesn't match the number required (1) for the requested transition'

In iOS5 all works good.

like image 862
Mattia Lancieri Avatar asked Sep 24 '12 12:09

Mattia Lancieri


4 Answers

I had the same issue. I found that the cause was replacing the delegate on the UIPanGestureRecognizer of the UIPageViewController, a no-no really. The pan gesture recognizer was calling an undocumented method _gestureRecognizerShouldBegin: (note the leading underscore) that UIPageViewController implements and apparently relies upon to work properly (read: not-crash). I ended up implementing respondsToSelector: and forwardingTargetForSelector: in my class that uses the UIPageViewController to pass the undocumented delegate method on to the UIPageViewController without specifically naming it (and almost certainly earning me an app store review rejection).

-(BOOL)respondsToSelector:(SEL)aSelector {
    if ([super respondsToSelector:aSelector])
        return YES;
    else if ([self.pageViewController respondsToSelector:aSelector])
        return YES;
    else
        return NO;
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if ([super respondsToSelector:aSelector]) {
        return nil;
    } else if ([self.pageViewController respondsToSelector:aSelector]) {
        return self.pageViewController;
    }
    return nil;
}

My longer term solution will be to rework the use of UIPageViewController such that I don't need to displace the gesture recognizer delegates.

like image 87
rrrus Avatar answered Nov 17 '22 05:11

rrrus


Ah,was wondering why no one has pointed out this bug,which i took almost 2 nights to find out the solution.

OLD CODE(iOS 5.1) : when returning nil on the first and last page you will experience the app crash.It works fine in iOS 5.1,but in iOS 6 it wont.

- (UIViewController *)pageViewController:
(UIPageViewController *)pageViewController viewControllerBeforeViewController:
(UIViewController *)viewController
{
    for (UIGestureRecognizer *recognizer in pageController.gestureRecognizers) {
        if ([recognizer isKindOfClass:[UITapGestureRecognizer class]]) {
            recognizer.enabled = NO;
        }
    }
    NSUInteger index = [self indexOfViewController:
                        (MainViewController *)viewController];
    if ((index == 0) || (index == NSNotFound)) {
        return nil;
    }

    index--;
    return [self viewControllerAtIndex:index];
}
- (UIViewController *)pageViewController:
(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
    for (UIGestureRecognizer *recognizer in pageController.gestureRecognizers) {
        if ([recognizer isKindOfClass:[UITapGestureRecognizer class]]) {
            recognizer.enabled = NO;
        }
    }
    NSUInteger index = [self indexOfViewController:
                        (MainViewController *)viewController];
    if (index == NSNotFound) {
        return nil;
    }
}

SOLUTION(iOS 6) : After adding the gesture effect to the superview,just call the delegate called -(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer. What i did is quiet simple, computing the speed of the user flipping the first page and last page (i mean using the gesture recognizer) , i denied the swiping.All you need to do is just paste the following code,and you are DONE!.

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
    if (pageNum==0) {

        if ([(UIPanGestureRecognizer*)gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] &&
            [(UIPanGestureRecognizer*)gestureRecognizer velocityInView:gestureRecognizer.view].x > 0.0f) {
            //NSLog(@"Swiping to left on 1st page is denied");
            return NO;
        }
        if ([(UITapGestureRecognizer*)gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]] &&
            [(UITapGestureRecognizer*)gestureRecognizer locationInView:gestureRecognizer.view].x < self.view.frame.size.width/2) {
         //NSLog(@"tapping to left on 1st page is denied");
            return NO;
        }
    }

    else if(pageNum ==totalNoOfFiles-1)
    {

        if ([(UIPanGestureRecognizer*)gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] &&
            [(UIPanGestureRecognizer*)gestureRecognizer velocityInView:gestureRecognizer.view].x < 0.0f) {
            //NSLog(@"Swiping to right on 1st page is denied");
            return NO;
        }
        if ([(UITapGestureRecognizer*)gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]] &&
            [(UITapGestureRecognizer*)gestureRecognizer locationInView:gestureRecognizer.view].x > self.view.frame.size.width/2) {
          //NSLog(@"Tapping to right on 1st page is denied");
            return NO;
        }
    }

    return YES;
}


- (UIViewController *)pageViewController:(UIPageViewController*) pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
 {
  int index = [self indexOfViewController:(ChildViewController *)viewController];
                index--;

    return [self viewControllerAtIndex:index];
}


- (UIViewController *)pageViewController:
(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
    int index = [self indexOfViewController:(ChildViewController *)viewController];

            index++;
     return [self viewControllerAtIndex:index];
}
like image 21
Master Stroke Avatar answered Nov 17 '22 04:11

Master Stroke


This has been well discussed but I have one thing to add. Consider why you were setting the delegate of the gesture recognizers to self. In my case, it was because in some cases I wanted to prevent the gesture recognizers from recognizing, with the delegate's gestureRecognizerShouldBegin:.

But in iOS 6, where this issue arises, there is a whole new way of doing exactly that, by implementing gestureRecognizerShouldBegin: on a UIView. (This is a new UIView instance method in iOS 6.)

Thus I was able to accomplish exactly what I was accomplishing before, without altering the gesture recognizers' delegate.

like image 20
matt Avatar answered Nov 17 '22 03:11

matt


I had the issue with UIPageViewController crashing with iOS6 with the same error ('The number of view controllers provided (0) doesn't match the number required (1) for the requested transition').

None of the above solutions worked for me but I eventually found that moving the following line from viewDidLoad to viewDidAppear fixed it.

self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;
like image 34
Liam Avatar answered Nov 17 '22 05:11

Liam