Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UIPageViewController, how do I correctly jump to a specific page without messing up the order specified by the data source?

I've found a few questions about how to make a UIPageViewController jump to a specific page, but I've noticed an added problem with jumping that none of the answers seem to acknowledge.

Without going into the details of my iOS app (which is similar to a paged calendar), here is what I'm experiencing. I declare a UIPageViewController, set the current view controller, and implement a data source.

// end of the init method         pageViewController = [[UIPageViewController alloc]          initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll           navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal                         options:nil];         pageViewController.dataSource = self;         [self jumpToDay:0]; }  //...  - (void)jumpToDay:(NSInteger)day {         UIViewController *controller = [self dequeuePreviousDayViewControllerWithDaysBack:day];         [pageViewController setViewControllers:@[controller]                                     direction:UIPageViewControllerNavigationDirectionForward                                      animated:YES                                    completion:nil]; }  - (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {         NSInteger days = ((THDayViewController *)viewController).daysAgo;         return [self dequeuePreviousDayViewControllerWithDaysBack:days + 1]; }  - (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {         NSInteger days = ((THDayViewController *)viewController).daysAgo;         return [self dequeuePreviousDayViewControllerWithDaysBack:days - 1]; }  - (UIViewController *)dequeuePreviousDayViewControllerWithDaysBack:(NSInteger)days {         return [[THPreviousDayViewController alloc] initWithDaysAgo:days]; } 

Edit Note: I added simplified code for the dequeuing method. Even with this blasphemous implementation I have the exact same problem with page order.

The initialization all works as expected. The incremental paging all works fine as well. The issue is that if I ever call jumpToDay again, the order gets jumbled.

If the user is on day -5 and jumps to day 1, a scroll to the left will reveal day -5 again instead of the appropriate day 0. This seems to have something to do with how UIPageViewController keeps references to nearby pages, but I can't find any reference to a method that would force it to refresh it's cache.

Any ideas?

like image 675
Kyle Avatar asked Nov 29 '12 19:11

Kyle


2 Answers

Programming iOS6, by Matt Neuburg documents this exact problem, and I actually found that his solution feels a little better than the currently accepted answer. That solution, which works great, has a negative side effect of animating to the image before/after, and then jarringly replacing that page with the desired page. I felt like that was a weird user experience, and Matt's solution takes care of that.

__weak UIPageViewController* pvcw = pvc; [pvc setViewControllers:@[page]               direction:UIPageViewControllerNavigationDirectionForward                animated:YES completion:^(BOOL finished) {                    UIPageViewController* pvcs = pvcw;                    if (!pvcs) return;                    dispatch_async(dispatch_get_main_queue(), ^{                        [pvcs setViewControllers:@[page]                                   direction:UIPageViewControllerNavigationDirectionForward                                    animated:NO completion:nil];                    });                }]; 
like image 92
djibouti33 Avatar answered Oct 16 '22 09:10

djibouti33


So I ran into the same problem as you where I needed to be able to 'jump' to a page and then found the 'order messed up' when I gestured back a page. As far as I have been able to tell, the page view controller is definitely caching the view controllers and when you 'jump' to a page you have to specify the direction: forward or reverse. It then assumes that the new view controller is a 'neighbor' to the previous view controller and hence automagically presents the previous view controller when you gesture back. I found that this only happens when you are using the UIPageViewControllerTransitionStyleScroll and not UIPageViewControllerTransitionStylePageCurl. The page curl style apparently does not do the same caching since if you 'jump' to a page and then gesture back it delivers the pageViewController:viewController(Before/After)ViewController: message to the data source enabling you to provide the correct neighbor view controller.

Solution: When performing a 'jump' to page you can first jump to the neighbor page to the page (animated:NO) you are jumping to and then in the completion block of that jump, jump to the desired page. This will update the cache such that when you gesture back, the correct neighbor page will be displayed. The downside is that you will need to create two view controllers; the one you are jumping to and the one that should be displayed after gesturing back.

Here is the code to a category that I wrote for UIPageViewController:

@implementation UIPageViewController (Additions)   - (void)setViewControllers:(NSArray *)viewControllers direction:(UIPageViewControllerNavigationDirection)direction invalidateCache:(BOOL)invalidateCache animated:(BOOL)animated completion:(void (^)(BOOL finished))completion {     NSArray *vcs = viewControllers;     __weak UIPageViewController *mySelf = self;      if (invalidateCache && self.transitionStyle == UIPageViewControllerTransitionStyleScroll) {         UIViewController *neighborViewController = (direction == UIPageViewControllerNavigationDirectionForward                                                     ? [self.dataSource pageViewController:self viewControllerBeforeViewController:viewControllers[0]]                                                     : [self.dataSource pageViewController:self viewControllerAfterViewController:viewControllers[0]]);         [self setViewControllers:@[neighborViewController] direction:direction animated:NO completion:^(BOOL finished) {             [mySelf setViewControllers:vcs direction:direction animated:animated completion:completion];         }];     }     else {         [mySelf setViewControllers:vcs direction:direction animated:animated completion:completion];     } }  @end 

What you can do to test this is create a new 'Page-Based Application' and add a 'goto' button that will 'jump' to a certain calendar month and then gesture back. Be sure to set the transition style to scroll.

like image 33
Spencer Hall Avatar answered Oct 16 '22 11:10

Spencer Hall