Reputation: 613
i've got a question regarding a UIScrollView with paging enabled that contains many UIView, each managed by an own UIViewController.
Right now there are about 20 to 30 UIViewControllers that COULD be contained in the UIScrollView. It's a catalog app on the iPad, and I started preloading all the views at the beginning, but with the amount of UIViewControllers getting bigger and bigger, that is not an option any more.
I'm looking for the perfect solution in terms of memory usage. It's no problem to reload the UIViewControllers when the ContentOffset of the ScrollView reaches a specific controller. And I think to nil the UIViewControllers when the ContentOffset tells me that the UIViewControllers is not needed any more isn't that hard as well.
What is the correct way to handle this? Is it enough to alloc the UIViewControllers when needed, putting them into a NSMutableDictionary or NSMutableArray and nil them when they are not needed any more? A little bit of help from someone already having done something similar would be great!
Thanks for your help!
Upvotes: 1
Views: 2496
Reputation: 438287
I'm sure there are some good infinite scrolling classes out there, but if you were going to "roll your own", here is a minimalist bit of code that demonstrates the process of infinite scrolling, keeping the current, previous, and next pages in memory, but letting go of anything else. This assumes that:
page
property to keep track of what page it's for; andThus, it might look like:
- (void)viewDidLoad
{
[super viewDidLoad];
// my underlying model is just an array of strings, which I'll show on my child
// view; your model will be more elaborate, but I just want to illustrate the concept
self.objects = @[@"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9"];
// set the `contentSize` for the scrollview
CGRect content = self.view.bounds;
content.size.width *= [self.objects count]; // make it wide enough to hold everything
self.scrollView.contentSize = content.size;
// set our current page and load the first pages (the first and the next pages)
self.currentPage = 0;
[self addChildPage:0 toScrollView:self.scrollView];
[self addChildPage:1 toScrollView:self.scrollView];
}
- (void)addChildPage:(NSInteger)page toScrollView:(UIScrollView *)scrollView
{
// create the child controller
ChildViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:@"child"];
// set whatever properties you need to in order for it to present its information correctly
controller.text = self.objects[page];
controller.page = page;
// now do the stuff to add it to the right place in the scrollview
CGRect frame = self.view.bounds;
frame.origin.x = frame.size.width * page;
controller.view.frame = frame;
[self addChildViewController:controller]; // containment call for adding child view controller
[scrollView addSubview:controller.view];
[controller didMoveToParentViewController:self]; // containment call when done adding child
}
- (ChildViewController *)childControllerForPage:(NSInteger)page
{
for (ChildViewController *controller in self.childViewControllers)
{
if (controller.page == page)
return controller;
}
return nil;
}
- (void)addChildIfNecessary:(NSInteger)page toScrollView:(UIScrollView *)scrollView
{
if (page < 0 || page >= [self.objects count])
return;
ChildViewController *controller = [self childControllerForPage:page];
if (controller == nil)
[self addChildPage:page toScrollView:scrollView];
}
- (void)removeChildController:(UIViewController *)controller
{
[controller willMoveToParentViewController:nil]; // containment call before removing child
[controller.view removeFromSuperview];
[controller removeFromParentViewController]; // containment call to remove child
}
- (void)updateChildrenViewsForPage:(NSInteger)page forScrollView:(UIScrollView *)scrollView
{
if (page == self.currentPage)
return;
// add child pages as necessary
[self addChildIfNecessary:page toScrollView:scrollView];
[self addChildIfNecessary:(page-1) toScrollView:scrollView];
[self addChildIfNecessary:(page+1) toScrollView:scrollView];
// find any pages that need removing
NSMutableArray *pagesToRemove = [NSMutableArray array];
for (ChildViewController *controller in self.childViewControllers)
{
if (controller.page < (page - 1) ||
controller.page > (page + 1))
{
[pagesToRemove addObject:controller];
}
}
// remove them if they need removing
for (UIViewController *controller in pagesToRemove)
{
[self removeChildController:controller];
}
// update our "current page" index
self.currentPage = page;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
NSInteger page = scrollView.contentOffset.x / scrollView.frame.size.width + 0.5;
[self updateChildrenViewsForPage:page forScrollView:scrollView];
}
This demonstrates the appropriate custom container calls and the handling of the scroll events. I hope this helps.
Upvotes: 5