I'm trying to setup a UIPageViewController
that can scroll infinitely by dynamically loading page views via an NSUrlConnection
data pull. I start with 3 views: prev,curr,next; and load an additional view when the first or last view in the pageData
array is reached.
The trouble is that I load the additional views in viewControllerBeforeViewController
or viewControllerAfterViewController
. It seems that these methods can be called 2-3 times when doing a single swipe between pages. They can also be called during a half-swipe that never finishes the page transition. This can mean that multiple preloads start getting done prematurely. Then somehow the pageViewController
forgets the current location.
[self.pageViewController setViewControllers:@[[self.pageData objectAtIndex:gotoIdx]] direction:direction animated:NO completion:nil];
This line is used below to signal an addition of data in the pageViewController
, and to go to eh appropriate view. When I start using it, instead of going to the prev/next view with a swipe, it starts jumping to seemingly random views. Once I started going forward when swiping backwards...
Is there a more appropriate place to do the preload that is only called once after the page transition completes? And, does the line above have to be called, or is there a more reliable way to ensure it goes to the correct page? Feeling frustrated trying to wire this up here.
// the init line, somewhere early on. uses Scoll style
self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll navigationOrientation:UIPageViewControllerNavigationOrientationVertical options:nil];
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
NSUInteger index = [self.pageData indexOfObject:viewController];
if(index == 1){
// if loading last one on start, preload one more in advance
[self loadSecondaryCalData:-1 endView:[[self viewControllerAtIndex:index-1] retain]];
} else if ((index == 0) || (index == NSNotFound)) {
return nil;
}
index--;
return [self viewControllerAtIndex:index];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {
NSUInteger index = [self.pageData indexOfObject:viewController];
if(index == [self.pageData count]-2){
[self loadSecondaryCalData:1 endView:[[self viewControllerAtIndex:index+1] retain]]; // if last one, preload one more in advance
} else if (index == NSNotFound) {
return nil;
}
index++;
if (index == [self.pageData count]) {
return nil;
}
return [self viewControllerAtIndex:index];
}
- (void)loadSecondaryCalData:(int)howMany endView:(CalendarViewController*)endView {
// initiate NSURLConnection async request for view data
}
- (void)loadSecondaryCals: (NSDictionary*)data {
// callback for async request
// ... process view, cal
// add new object to page data, at start or end
if(next){
[self.pageData addObject:cal];
gotoIdx = [self.pageData count]-2;
direction = UIPageViewControllerNavigationDirectionForward;
} else {
[self.pageData insertObject:cal atIndex:0];
gotoIdx = 1;
direction = UIPageViewControllerNavigationDirectionReverse;
}
[self.pageViewController setViewControllers:@[[self.pageData objectAtIndex:gotoIdx]] direction:direction animated:NO completion:nil];
// alternate method to above line
/*
__block CalendarPageViewViewController *blocksafeSelf = self;
__block int blocksafeGotoIdx = gotoIdx;
__block UIPageViewControllerNavigationDirection blocksafeDirection = direction;
[self.pageViewController setViewControllers:@[[self.pageData objectAtIndex:gotoIdx]] direction:direction animated:YES completion:^(BOOL finished){
if(finished)
{
dispatch_async(dispatch_get_main_queue(), ^{
[blocksafeSelf.pageViewController setViewControllers:@[[blocksafeSelf.pageData objectAtIndex:blocksafeGotoIdx]] direction:blocksafeDirection animated:NO completion:nil];
});
}
}];
*/
}
UPDATE:
Thanks to the fast response below, referencing a great function I wasn't aware of. Here's the preload method that so perfectly only gets called once on completion of the transition. This seems to have greatly cleared up the unreliable jumping around of views and forgetting of location. Thanks @sha!
// function to detect if we've reached the first or last item in views
// detects for full completion only
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed {
if(completed == TRUE && [previousViewControllers count] > 0 && [pageData count] > 1){
CalendarViewController *firstCal = [pageData objectAtIndex:0];
CalendarViewController *lastCal = [pageData objectAtIndex:([pageData count]-1)];
CalendarViewController *prevCal = [previousViewControllers objectAtIndex:0];
CalendarViewController *currCal = [[pageViewController viewControllers] objectAtIndex:0];
NSLog(@"transition completed: %@ -> %@", prevCal.week_day_date, currCal.week_day_date);
if(currCal == firstCal){
// preload on begining
[self loadSecondaryCalData:-1 endView:firstCal];
} else if(currCal == lastCal){
// preload to end
[self loadSecondaryCalData:1 endView:lastCal];
}
}
}
I think you need to do your loading calls inside pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:
handler. This method will be called when animation has finished and new ViewController became an active one.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With