Uipageviewcontroller Gesture Is Calling Viewcontrollerafter: But Doesn't Animate

UIPageViewController gesture is calling viewControllerAfter: but doesn't animate

Please look at my other answer in the first place, this one has serious flaws but I'm leaving it here as it might still help someone.


First off, a disclaimer: The following solution is a HACK. It does work in the environment I tested but there is no guarantee that it works in yours nor that it won't be broken by the next update. So proceed with care.

TL;DR: grab the UIPanGestureRecognizer of the UIPageViewController and hijack its delegate calls but keep forwarding them to the original target.

Longer version:

My findings on the issue: the UIPageViewController shipped in iOS 6 is different in behavior to the one in iOS 5 in that it may call the pageViewController:viewControllerBeforeViewController: on its datasource even if there is no page turning going on in any sense (read: no tap, swipe, or valid direction-matching panning has been recognized). This, of course, breaks our former assumption that the before/after calls are equivalent to an "animation begin" trigger and are consistently followed by a pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted: call to the delegate. (Eventually this is a bold assumption to make but I guess I was not alone with that.)

I found out that the extra calls to the datasource are likely to happen when the default UIPanGestureRecognizer on the page view controller starts to recognize a pan gesture that in the end doesn't match the direction of the controller (e.g. vertical panning in a horizontally paging PVC). Interestingly enough, in my environment it was always the "before" method which got hit, never the "after". Others suggested interfereing with the gesture recognizer's delegate but that didn't work for me the way it was described there so I kept experimenting.

Finally I found a workaround. First we grab the pan gesture recognizer of the page view controller:

@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;
}
}

Then we implement the UIGestureRecognizerDelegate protocol in our class:

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

Apparently, the methods don't do anything sensible, they just forward the invocations to the original delegate (making sure that that actually implements them). Still, this forwarding seems to be sufficient for the PVC to behave and not call the datasource when there is no need to.

This workaround fixed the issue for me on devices running iOS 6. Code which was compiled with the iOS 6 SDK but with a deployment target of iOS 5 had already run flawlessly on 5.x devices, so the fix is not necessary there but according to my tests it doesn't do any harm either.

Hope someone finds this useful.

UIPageViewController not working properly

I'm not sure but, I think the problem is in the gesture recognizers, or the location of there CGRects. Basically the page view controller listens for a touches-move. So if the user swipes the scroll (on the webView) page view tries to execute a page turn. (I'm guessing) I think a solution would be to make sure the page view touch location (CGRect) does not overlap with the web view. Apparently you can set the position (region) for the touch for the UIPageViewController" to turn the page.

You can start by looking up "UIGestureRecognizer Class Reference" in the iOS Reference Library. I hope this helps

Here's what I found there:

locationInView:
Returns the point computed as the location in a given view of the gesture represented by the receiver.

  • (CGPoint)locationInView:(UIView *)view
    Parameters
    view
    A UIView object on which the gesture took place. Specify nil to indicate the window.
    Return Value
    A point in the local coordinate system of view that identifies the location of the gesture. If nil is specified for view, the method returns the gesture location in the window’s base coordinate system.

Discussion
The returned value is a generic single-point location for the gesture computed by the UIKit framework. It is usually the centroid of the touches involved in the gesture. For objects of the UISwipeGestureRecognizer and UITapGestureRecognizer classes, the location returned by this method has a significance special to the gesture. This significance is documented in the reference for those classes.

Availability
Available in iOS 3.2 and later.
See Also
– locationOfTouch:inView:
Declared In
UIGestureRecognizer.h
locationOfTouch:inView:
Returns the location of one of the gesture’s touches in the local coordinate system of a given view.

  • (CGPoint)locationOfTouch:(NSUInteger)touchIndex inView:(UIView *)view
    Parameters
    touchIndex
    The index of a UITouch object in a private array maintained by the receiver. This touch object represents a touch of the current gesture.
    view
    A UIView object on which the gesture took place. Specify nil to indicate the window.
    Return Value
    A point in the local coordinate system of view that identifies the location of the touch. If nil is specified for view, the method returns the touch location in the window’s base coordinate system.

Availability
Available in iOS 3.2 and later.
See Also
– locationInView:
Declared In
UIGestureRecognizer.h

In ios6, setting your pageViewController's gestureRecognizers delegate to a viewController causes a crash

Finally found a solution to my problem which has caused me grief so hopefully this can help someone else.

The issue is if you set the pageViewControllers delegate to your viewController

for (UIGestureRecognizer *gR in self.pageController.view.gestureRecognizers) 
{
if ([gR isKindOfClass:[UITapGestureRecognizer class]])
{
gR.enabled = NO;
}
else if ([gR isKindOfClass:[UIPanGestureRecognizer class]])
{
gR.delegate = self;
}
}

then returning nil from

pageViewController:viewControllerAfterViewController:

will crash!! only in iOS6!!

My issue was that I needed to set the delegate of the gestureRecognisers because I needed to intercept the panGesture in some situations, i.e not allowing the user to turn a page wilst touching certain parts of it due to some buttons there.

The solution is to put the logic from

pageViewController:viewControllerAfterViewController: 

into

gestureRecognizer:shouldReceiveTouch:

because as long as we return NO from that then it wont go on to call

pageViewController:viewControllerAfterViewController:

and so no need to return nil and get a crash.

However, this didn't work on the first and last page in the sequence. For example on the first page, you want to allow the page to turn forward but not back. So I thought about looking at the GestureRecogniser passed in, casting it to a PanGesture, and then checking the velocity on this, if the velocity signifies turning back ( > 0.0f ) then just return NO. This sounded good but the velocity was always zero.

I then found a very helpful little function on the GestureRecognizer delegate called:

gestureRecognizerShouldBegin:gestureRecognizer

this function is called after

gestureRecognizer:shouldReceiveTouch:

but this time the velocity from the gesture is as I expected, so I can check the velocity and only return YES for the first page if it is > 0.0f

UIPageViewController viewControllerAfterViewController never called

The problem was that in including my UIViewController subclass which encapsulated the UIPageViewController, I forgot in the parent view controller's viewDidLoad to include addChildViewController and didMoveToParentViewController

[self addChildViewController:myViewController];
[self.view addSubview:myViewController.view];
[myViewControllerController didMoveToParentViewController:self];

Fixed the problem.

UIPageViewController responds to vertical pan when orientation is set to horizontal

I had initially stumbled across your question facing the same problem - but I seem to have fixed it for my situation.

Basically, what I had to do was set a delegate to all the gesture recognizers attached to the UIPageViewController. So, soon after creating the UIPageViewController, I did:

for (UIGestureRecognizer *gr in self.book.gestureRecognizers)
gr.delegate = self.book;

where book is my custom UIPageViewController (I had essentially set itself as the delegate). Finally, add this method to your UIPageViewController to restrict any vertical panning (or use the commented out line instead to restrict horizontal panning).

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]])
{
UIPanGestureRecognizer *panGestureRecognizer = (UIPanGestureRecognizer *)gestureRecognizer;
CGPoint translation = [panGestureRecognizer translationInView:self.view];

return fabs(translation.x) > fabs(translation.y);
// return fabs(translation.y) > fabs(translation.x);
}
else
return YES;
}

I was hinted towards this thanks to this answer - just make sure you filter out the UIPanGestureRecognizers only.



Related Topics



Leave a reply



Submit