STATEMENT:
It seems that Apple has spotted that developers are using UIPageViewController in very different applications that go way beyond the
originally intended ones Apple based their design-choices on in the first place. Rather than using it in a gesture driven linear fashion
PVC is often used to programmatically jump to random
positions within a structured environment. So they have enhanced their implementation of UIPageViewController and the class is now calling both DataSource
callbacks
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
after setting a new contentViewController on UIPageViewController with
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
even if an animated turn of pages rather suggests a linear progress in an e.g. page hierarchy like a book or PDF with consecutive pages. Although - I doubt that Apple from a HIG standpoint
is very fond of seeing PVC being used this way, but - it doesn't break backwards compatibility, it was an easy fix, so - they eventually did it. Actually it is just one more call of one of the two DataSource methods that is absolutely unnecessary in a linear environment where pages (ViewControllers) have already been cashed for later use.
However, even if this enhancement might come in very handy for certain use-cases the initial behavior of the class is NOT to be considered a bug. The fact that a lot of developers do - also in other
posts on SO that accuse UIPageViewController of misbehavior - rather emphasizes a widely spread misconception of its design, purpose and functionality.
Without trying to offend any of my fellow developers here in this great facility I nonetheless decided not to remove my initial 'disquisition' that clearly explains to the OP the mechanics of PVC and why his assumption is wrong that he has to deal with a bug here.
This might also be of use for any other fellow developer too who struggles with some intricacies in the implementation of UIPageViewController!
ORIGINAL ANSWER:
After having read all the answers over and over again - included the
accepted one - there is just one more thing left to say...
The design of UIPageViewController
is absolutely FLAWLESS and all the
hacks you submit in order to circumvent an alleged bug is nothing but
remedies for your own faulty assumptions because you goofed it up in the
first place!!!
THERE IS NO BUG AT ALL! You are just fighting the framework. I'll explain why!
There is so much talk about page numbers and indices! These are concepts the
controller knows NOTHING about! The only thing it knows is - it is showing
some content (btw. provided by you as a dataViewController) and that it can
do something like a right/left animation in order to imitate a page turn.
CURL
or SCROLL
...!!!
In the pageViewController's world there only exists a current SPACE
(let's call
it just this way to avoid confusion with pages and indices).
When you initially set a pageViewController it only minds about this very SPACE
.
Only when you start panning its view it starts asking its DataSource
what it
eventually should display in case a left/right flip should happen. When you start
panning to the left the PVC asks first for the BEFORE-SPACE
and then for the
AFTER-SPACE
, in case you start to the right it does it the other way round.
After the completed animation (a new SPACE
is displayed by the PVC's view) the
PVC considers this SPACE
as its new center of the universe and while it is at it, it
asks the DataSource
about the one it still does not know anything about. In case of
a completed turn to the right it wants to know about the new AFTER
space and in
case of a completed turn to the left it asks for a new BEFORE
space.
The old BEFORE
space (from before the animation) is in case of a completed turn to
the right completely obsolete and gets deallocated as soon as possible. The old center
is now the new BEFORE
and the former AFTER
is the new center
. Everything just
shifted one step to the right.
So - no talk of 'which page' or 'whatever index' - just simply - is there a BEFORE
or
an AFTER
space. If you return NIL to one of the DataSource
callbacks the PVC just
assumes it is at one extreme of your range of SPACES
. If you return NIL to both
callbacks it assumes it is showing the one and only SPACE
there is and will never
ever again call a DataSource
callback anymore! The logic is up to you! You define
pages and indices in your code! Not the PVC!!!
For the user of the class there are two means of interacting with the PVC.
A pan-gesture that indicates whether a turn to the BEFORE/AFTER space is desired
A method - namely setViewControllers:direction:animated:completion:
This method does exactly the same than the pan gesture is doing. You are indicating the
direction (e.g. UIPageViewControllerNavigationDirectionBackward/Forward
)
for the animation - if there is one intended - which in other words just means -> going to
BEFORE
or AFTER
...
Again - no mentioning of indices, page-numbers etc....!!!
It is just a programmatically way of achieving the same a gesture would!
And the PVC is doing right by showing the old content again when moving back
to the left after having moved to the right in the first place. Remember
- it is just showing content (that you provide) in a structured way - which is a 'single page turn'
by design!!!
That is the concept of a page turn - or BOOK, if you like that term better!
Just because you goof it up by submitting PAGE 8 after PAGE 1 doesn't mean the PVC
cares at all about your twisted opinion of how a book should work. And the user of your
apps neither. Flipping to the right and back to the left should definitely result in reaching
the original page - IF done with an animation. And it is up to YOU to correct the goof by
finding a solution for the disaster. Don't blame it on the UIPageViewController
. It is doing
its job perfectly!
Just ask yourself - would you do the same thing with a PAGE-CURL
animation? NO ?
Well, neither should you with a SCROLL
animation!!! An animated page turn is a page turn and only a page turn!
In either mode!
And if you decide to tear out PAGE 2 to PAGE 7 of your BOOK that's perfectly fine!
But just don't expect UIPageViewController
to invent a non-existing PAGE 7 when turning back to the recent page unless YOU tell it that things have changed...
If you really want to achieve an uncoordinated jump to elsewhere, well - do it without an
animation! In most cases this will not be very elegant but - it's possible... -
And the PVC even plays nicely along! When jumping to a new SPACE
without animation
it will ask you further down the road for both - the BEFORE
and AFTER
controller. So your application-logic can keep up with the PVC...
But with an animation you are always conveying - move to the previous/next space (BEFORE -
AFTER
). So logically there is no need at all for the PVC to ask again about a space it already
knows about when animating page turns!!!
If you wanna see PAGE 7 when flipping back to the left after having animated from PAGE 1
to the right - well, I would say - that's definitely your very own problem!
And just in case you are looking for a better solution than the 'completion-block' hack from
the accepted answer (because with it you are doing work beforehand for something that might
possibly not even get used further down the road) use the gesture recognizer delegate:
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
Set your PVC's DataViewController here (without animation) if you really intend to go back
left to PAGE 7 and the DataSource
will be asked for BEFORE
and AFTER
and you can submit
whatever page you like! With a flag or ivar that you should have stashed away when doing your uncontrolled
jump from PAGE 1 to 8 this should be no problem...
And when people keep on complaining about a bug in the PVC - doing 2 page turns when it is
supposed to do 1 turn only - point them to this article.
Same problem - triggering an un-animated setViewControllers: method within the transition gesture
will cause exactly the same havoc. You think you set the new center - the DataSource is asked
for the new BEFORE - AFTER
dataController - you reset your index count... - Well, that seems OK...
But - after all that business the PVC ends its transition/animation and wants to know about the
next (still unknown to it) dataViewController (BEFORE
or AFTER
) and also triggers the DataSource
. That's totally justified ! It needs to know where in its small BEFORE - CENTER - AFTER
world it is and be prepared for the next turn.
But your program-logic adds another index++ count to its logic and suddenly got 2 page turns !!!
And that is one off from where you think you are.
And YOU have to account for that! Not UIPageViewController
!!!
That is exactly the point of the DataSourceProtocol only having two methods! It wants to be as generic as possible - leaving you the space and freedom to define your own logic and not being stuck with somebody else's special ideas and use-cases! The logic is completely up to you. And only because you find functions like
- (DataViewController *)viewControllerAtIndex:(NSUInteger)index storyboard:(UIStoryboard *)storyboard position:(GSPositionOfDataViewController)position;
- (NSUInteger)indexOfViewController:(DataViewController *)viewController;
in all the copy/pasted sample applications in the cloud doesn't necessarily mean that you have to eat that pre-cook food! Extend them any way you like! Just look above - in my signature you will find a 'position:'
argument! I extended this to know later on if a completed page turn was a right or a left turn. Because the delegate unfortunately just tells you whether your turn completed or not! It doesn't tell you about the direction! But this sometimes matters for index-counting, depending on your application's need...
Go crazy - they are your's...
HAPPY CODING !!!