I have a month view similar to the iOS calendar and an UICollectionView
is used. Now it would be interesting to implement an infinite scrolling behavior so that the user can scroll in each direction vertically and it will never end. The question now is how can such a behavior be implemented in an efficient way? This is what I've found out now:
Basically you can check if you hit the end of the current scroll view. You can check this in scrollViewDidScroll:
or in collectionView:cellForItemAtIndexPath:
. It would be simple to add another content to the datasource, but I think there is more than that. If you only add data you could only scroll downwards for example. The user should be able to scroll in both directions (upwards, downwards). Don't know if reloadData
would do the trick. Also the contentOffset
would change and there should be no jumping behavior.
Another possibility would be to use the approach shown in Advanced ScrollView Techniques of WWDC 2011. Here layoutSubviews
is used to set the contentOffset
to the center of the UIScrollView
and the frames of the subviews are adjusted to the same amount of the distance from the center. This approach would work fine if I have no sections. How would this work with sections?
I don't want to use a high value for the number of sections to fake a infinite scroll, because user will find the end. Also I don't use any paging.
So how can I implement infinite scrolling for the collection view?
Edit:
Now I tried to increase the number of section if I hit the end of the UICollectionView
. To show the new sections one has to call reloadData
. On calling this method all calculations for all current available sections are done again! This performance issue is causing big stutters when scrolling through the collection view and it gets slower and slower if you scroll down. Don't know if one could transfer this work on a background thread. With this approach one could scroll upwards and downwards if you make the needed adaptions.
Bounty:
Now I'm offering a bounty for answering this question. I'm interested in how the month view of the iOS calendar is implemented. In detail how does the infinite scrolling works. Here it works in both directions (upwards, downwards) and it never ends (real infinite - no repeating). Also there is no lag at all (even on an iPhone 4). I want to use the UICollectionView
and the data consists of different sections and each section has a different number of items. One has to do some calculations to get the next section. I don't need the calendar part - only the infinite scrolling behavior with the different items in a section. Feel free to ask question.
Adding Sections:
public override void Scrolled(UIScrollView scrollView)
{
NSIndexPath[] currentIndexPaths = currentVisibleIndexPaths();
// if we are at the top
if (currentIndexPaths.First().Section == 0)
{
NSIndexPath oldIndexPath = NSIndexPath.FromItemSection(0, 0);
UICollectionViewLayoutAttributes attributes_before = this.controller.CollectionView.GetLayoutAttributesForItem(oldIndexPath);
CGRect before = attributes_before.Frame;
CGPoint contentOffset = this.controller.CollectionView.ContentOffset;
this.controller.CollectionView.PerformBatchUpdatesAsync(delegate ()
{
// some calendar calculations and updating the data source not shown here
this.controller.CurrentNumberOfSections += 12;
this.controller.CollectionView.InsertSections(NSIndexSet.FromNSRange(new NSRange(0, 12)));
}
);
NSIndexPath newIndexPath = NSIndexPath.FromItemSection(0, 12);
UICollectionViewLayoutAttributes attributes_after = this.controller.CollectionView.GetLayoutAttributesForItem(newIndexPath);
CGRect after = attributes_after.Frame;
contentOffset.Y += (after.Y - before.Y);
this.controller.CollectionView.SetContentOffset(contentOffset, false);
}
// if we are near the end
if (currentIndexPaths.Last().Section == this.controller.CurrentNumberOfSections - 1)
{
this.controller.CollectionView.PerformBatchUpdatesAsync(delegate ()
{
// some calendar calculations and updating the data source not shown here
this.controller.CollectionView.InsertSections(NSIndexSet.FromNSRange(new NSRange(this.controller.CurrentNumberOfSections, 12)));
this.controller.CurrentNumberOfSections += 12;
}
);
}
}
If we are near the top the app crashes with
Snapshotting a view that has not been rendered results in an empty snapshot. Ensure your view has been rendered at least once before snapshotting or snapshot after screen updates. Assertion failure in -[Procet_UICollectionViewCell _addUpdateAnimation], /SourceCache/UIKit_Sim/UIKit-2935.137/UICollectionViewCell.m:147
I think it crashes because it is called too often. If I remove the contentOffset adaptions it does work, but I'm always on top. If I'm on top more and more sections are added. So this algorithm needs to be restricted. I also have an initial content offset. This offset is wrong because on initialization the algorithm is also called and adds some sections. Now I tried to add the sections in didEndDisplayingCell
but it crashes.
Adding sections at the end does work, but it doesn't matter when I add it (one section before or 10 sections before). When the update takes place the scrolling has some stutter. Another thing I tried was to decrease the number of sections from 12 to 3, but then more and more stutter occur.
performBatchUpdates
. The advantage here is that only the calculations for the new inserted sections are done. Nevertheless, I have a small stutter when a new year is appearing. If you scroll fast than the scrolling will end and you have to scroll again because of the stutter. Is there a possibility to offload this task to a background thread so that the user never get the chance of seeing the stutter? Another thing: How can I scroll upwards infinitively? Adding at the end is no problem. But for the beginning I think I have to do all calculations again. – Assiniboine