23
votes

I have a UICollectionView that is used to simulate the new calendar in iOS 7. This collection view is inside a controller that has a selectedDate property. Whenever the selectedDate property is set the collection view should scroll to the date in the collection view.

The calendar controller's viewWillAppear also ensure the selected date is visible because this controller is cached and reused.

-(void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    [self.calendarView scrollToDate:[self selectedDate] animated:NO];
}

The problem is that the VERY first time the calendar controller is shown the scroll does not work. The contentOffset of the collection view is not updated.

My current workaround is to schedule the scroll to occur on the next run loop using

dispatch_after(DISPATCH_TIME_NOW, dispatch_get_main_queue(), ^(void)
{
        // Scroll to the date.
    });

It looks like when the UICollectionView is not in a window you cannot scroll. Scheduling the scroll to happen on the next run loop ensure that the view has been added to the window and can be properly scrolled.

Has anyone else experienced this issue and what their workarounds?

4
I'm experiencing the same issue (ironically, after building a similar calendar control). My solution so far has been to animate the scroll during viewDidAppear:enjayem

4 Answers

23
votes

If you are using auto layout, the issue may be that the constraints haven't set the frames yet. Try calling the scrollToDate: method in viewDidLayoutSubviews (without dispatch_after).

@interface CustomViewController ()

@property (nonatomic) BOOL isFirstTimeViewDidLayoutSubviews; // variable name could be re-factored

@property (nonatomic, weak) IBOutlet UIScrollView *scrollView;

@end

@implementation CustomViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.isFirstTimeViewDidLayoutSubviews = YES;
}

- (void)viewDidLayoutSubviews
{
    // only after layoutSubviews executes for subviews, do constraints and frames agree (WWDC 2012 video "Best Practices for Mastering Auto Layout")

    if (self.isFirstTimeViewDidLayoutSubviews) {

        // execute geometry-related code...

        // good place to set scroll view's content offset, if its subviews are added dynamically (in code)

        self.isFirstTimeViewDidLayoutSubviews = NO;
    }
23
votes

you can always force auto-layout to layout.

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    self.view.layoutIfNeeded()
    self.collectionView.scrollToItemAtIndexPath......
}
3
votes

bilobatum's answer is correct! I'm writing this because I don't have reputation to comment... :/

I tried bilobatum's answer in my project, and it worked perfectly! My code:

-(void)viewDidLayoutSubviews{
    [super viewDidLayoutSubviews];
    if (currentOffset.y != 999) {
        [collectionView setContentOffset:currentOffset animated:NO];
    }
}

currentOsset is a CGPoint initialized with x = 0 and y = 999 values (CGPoint currentOffset = {0,999};)

In the viewWillDisappear method I save the collectionView's contentOffset in the currentOffset. This way if I navigate to the controller that has the collectionView and I navigated to there before, I will always have the last position.

The code that will work for you:

-(void)viewDidLayoutSubviews{
    [super viewDidLayoutSubviews];
    [self.calendarView scrollToDate:[self selectedDate] animated:NO];
}

Thank you bilobatum for the answer!

0
votes

Using -viewDidLayoutSubviews created an infinite loop that made the solution too complicated.
Instead I just added a small delay to let the constraints be created before the scrolling:

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    if ([self.scheduleDate isThisWeek]) [self.calendarLayout performSelector:@selector(scrollToCurrentTime) withObject:nil afterDelay:1];
}