2

This may look like a previously asked question(PageViewController delegate functions called twice) but the thing is I could not apply that solution to my problem.

As you might notice that I m developing a calendar application and Using UIPageViewController to manage my yearly calendar display. As you can see I'm using UIPageViewControllerTransitionStylePageCurl and when user curls the page(either forward/backward) their respective delegate is getting called twice which is giving me either 2 year increment or decrement based on the delegate that got executed. Now I need to find out what is causing this issue and stop it from getting executed twice.

I know it is important to return a viewController in those delegates which gives my next page or previous page thus I m just refreshing the viewController's view so that I can render the view with new data. I also tried another delegate called willTransitionToViewControllers but wont get me anywhere because willTransitionToViewControllers will get executed only after viewControllerAfterViewController and viewControllerBeforeViewController.

Someone help me understand and solve this issue.

 - (void)addCalendarViews
{
    CGRect frame = CGRectMake(0., 0., self.view.frame.size.width, self.view.frame.size.height);

    pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl
                                                           navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
                                                                    options:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:UIPageViewControllerSpineLocationNone] forKey:UIPageViewControllerOptionSpineLocationKey]];
    pageViewController.doubleSided = YES;
    pageViewController.delegate = self;
    pageViewController.dataSource = self;

    YearCalendarViewController *yearController = [[YearCalendarViewController alloc] init];


    NSArray *viewControllers = [NSArray arrayWithObject:yearController];

    [self.pageViewController setViewControllers:viewControllers
                                  direction:UIPageViewControllerNavigationDirectionReverse
                                   animated:YES
                                 completion:nil];

    [self addChildViewController:pageViewController];
    [self.view addSubview:pageViewController.view];

    [pageViewController didMoveToParentViewController:self];

     CGRect pageViewRect = yearController.view.bounds;
     self.pageViewController.view.frame = pageViewRect;


     viewCalendarMonth = [[SGMonthCalendarView alloc] initWithFrame:frame];
     [self.view addSubview:viewCalendarMonth];

     arrayCalendars = @[pageViewController.view, viewCalendarMonth];
}




 -(UIViewController*)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
 {
     NSDate *currentDate = [[SGSharedDate sharedManager] currentDate]; 
     NSDateComponents *currentDateComp = [NSDate returnDateComponentsForDate:currentDate];
     self.nextDate = [NSDate dateWithYear:currentDateComp.year+1 month:currentDateComp.month day:currentDateComp.day];
    [[SGSharedDate sharedManager] setCurrentDate:nextDate];


   {
      //I m reloading viewController's view here to display the new set of data for the increamented date.
   }
    NSLog(@"After Dates====>%@", nextDate);
    return viewController;
 }



 -(UIViewController*)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
 {
     NSDate *currentDate = [[SGSharedDate sharedManager] currentDate]; 
     NSDateComponents *currentDateComp = [NSDate returnDateComponentsForDate:currentDate];
     nextDate = [NSDate dateWithYear:currentDateComp.year-1 month:currentDateComp.month day:currentDateComp.day];
     [[SGSharedDate sharedManager] setCurrentDate:nextDate];


     {
      //I m reloading viewController's view here to display the new set of data for the decremented date.
   }
     NSLog(@"Before Dates====>%@", nextDate);
     return viewController;
}
Community
  • 1
  • 1
Srinivasan N
  • 611
  • 9
  • 20

1 Answers1

5

The UIPageController tends to cache ahead (and back) so when you swipe forward from view controller N , N+1 loads BUT so does N+2 silently so in the event you swipe twice your content is already loaded.

But since you have an invisible side-effect (incrementing date) in the delegate method you are getting hit by this.

My suggestion is to remove the dating side-effect from your delegate and bind the date code to the presented view controller somehow ( a delegate or a property ) then trap the didFinishAnimating method of the pageController.

- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed {

    if (completed) {

        if ([[self.pageViewController.viewControllers firstObject] isKindOfClass:[MyCalenderViewController class]]) {

            currentDate = [self workOutCurrentDateForVC:[self.pageViewController.viewControllers firstObject]];

        }

    }

}

Side effects are a code smell. Try and avoid.

EDIT

I just noticed you are recycling the view controller and reloading it with the new date point. Don't do this . You need to treat a view controller as a single page of your book. So generate a new one and set it up for the month...

Briefly as an example...

@interface YearViewController : UIViewController 

@property NSDate *focusedYear;

-(instancetype)initWithYear:(NSDate)adate;

@end

@implementation YearViewController

-(instancetype)initWithYear:(NSDate)adate {
     self = [super initWithNibName:nil bundle:nil];
     if(self) {
         self.focusedYear = adate;
         ....
     } 
}

@end

So in the page controller delegate you just serve up a new "page" based on the date that the previous one had (and the direction you are paging)

-(UIViewController*)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
 {
     YearViewController *yvc = (YearViewController *)viewController;

     NSDate *currentDate = yvc.focusedYear; 
     NSDateComponents *currentDateComp = [NSDate returnDateComponentsForDate:currentDate];
     NSDate *nextDate = [NSDate dateWithYear:currentDateComp.year+1 month:currentDateComp.month day:currentDateComp.day];

     YearViewController *freshYVC = [[YearViewController alloc] initWithDate:nextDate];

    return freshYVC;
 }

The comment about side effects still stands.

Warren Burton
  • 17,451
  • 3
  • 53
  • 73
  • Thanks for the tip to avoid recycling the view controller. Had mad problems because of this. Now i just create new ones like you showed in you'r example. Is there any side effect of this? Im using it for swiping trough "dates", so it kind of is "endless". I first wanted to reuse the view controller because of performance. – Daniel Storch Nov 08 '16 at 21:07
  • @DanielStorch Depends on your code but as long as your pages are getting deallocated at some point then thats a good start. You can check this quickly by placing an `NSLog` in the dealloc (OC) or deinit (Swift) methods. – Warren Burton Nov 08 '16 at 22:35
  • Sorry for the late update but the concept worked. This calendar feature became a major feature of the iOS app I was developing and the feature was awarded. @WarrenBurton thanks for giving me an idea on what to do next. – Srinivasan N Jun 16 '23 at 13:54