I have a dining menu app that scrapes the data from a website and redisplays it in a mobile format, displayed below:

enter image description here

If the user swipes left and right, the app will show the previous/next meal (ex. if current meal is lunch, swiping left will show dinner).

I'm trying to come up with a more efficient way of switching between menus. With my current implementation, each menu is a separate view controller.

When someone swipes right, it calls:

-(void)swipeleft:(UISwipeGestureRecognizer*)gestureRecognizer {
    //swipes to next meal
    MealType curMeal = currentMenu.type;
    int newMeal = [MenuLoader MealAfterMeal:curMeal];

    MenuTableController *newMenu = [self.storyboard instantiateViewControllerWithIdentifier:@"Menu"];
    newMenu.currentMeal = newMeal;

    [self.navigationController pushViewController:newMenu animated:YES];

When each view controller is created (including the first), it loads the menu in a background thread:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        MenuLoader *ml = [[MenuLoader alloc] init];
        currentMenu = [ml mealForType:_currentMeal Specificity:[self summaryPreference]];

        dispatch_async(dispatch_get_main_queue(), ^(void) {

Inside the mealForType:Specificity: call, I parse 2 separate web pages through NSURL, and combine the information from both sites:

NSURL *url = [NSURL URLWithString:urlString];
NSData *pageData = [NSData dataWithContentsOfURL:url];

Some of the optimizations I'm hoping to make are:

  • Preloading adjacent menus in another thread to reduce loading time when someone swipes
  • If someone swipes forward and then back, it should show the already loaded menu. Right now, it makes a fresh call to the server and reloads everything.
  • Right now, if someone swipes multiple times in a row before any of the menus finish loading, the app tries processing all of the server calls at once and this slows down the loading time. If someone does swipe repeatedly, the current menu should take precedence, and possibly the other calls should be terminated.

It sounds like a big overhaul of my design, so I was wondering if there was a better approach to loading menus than what I currently implemented.

  • \$\begingroup\$ When you swipe left and right, are you limited to just today's meals? Or if I swipe past the last meal of today, will the app load the first meal of tomorrow? \$\endgroup\$
    – nhgrif
    Dec 25 '14 at 2:13
  • \$\begingroup\$ As of now, the former. I would like to implement the latter at some point \$\endgroup\$
    – Mahir
    Dec 25 '14 at 3:44
  • \$\begingroup\$ You should use UIPageViewController. It does exactly what you're trying to accomplish manually. \$\endgroup\$
    – sha
    Dec 26 '14 at 22:41
  • 1
    \$\begingroup\$ Also you probably should ask this on StackOverflow, since it's more about implementation details rather then code review. \$\endgroup\$
    – sha
    Dec 27 '14 at 18:22

I recently created a similar layout for a project I'm working on using Apple's PageControl example as a base.

I used a UICollectionView for the top nav section and a UIScrollView for paging through view controllers. The main view controller is setup as below.

Main View Controller

In the viewDidLoad method for the I set up the content size for the scrollView and set paging to YES.

[self.containerScrollView setContentSize:CGSizeMake(CGRectGetWidth(self.containerScrollView.frame)*self.numberOfPages, 
[self.containerScrollView setPagingEnabled:YES];
[self.containerScrollView setShowsHorizontalScrollIndicator:NO];
[self.containerScrollView setShowsVerticalScrollIndicator:NO];
[self.containerScrollView setScrollsToTop:NO];
[self.containerScrollView setDelegate:self];

(Note: if you're using xibs/size classes, I ran into a problem where the scrollView's size is stated as 600x600px no matter which device is used until the ParentViewController's viewDidLayoutSubviews is called. I had to reset the scroll view's content size again.)

After this is set up, I load the first two view controllers:

- (void)loadScrollViewWithPage:(NSUInteger)page {

    UIViewController *viewController = <get your view controller>;

    if (viewController.view.superview == nil) {
        [self addChildViewController:viewController];
        CGRect frame = [self.containerScrollView frame];
        frame.origin.x = CGRectGetWidth(frame)*page;
        frame.origin.y = 0;
        [viewController.view setFrame:frame];

        [self.containerScrollView addSubview:viewController.view];
        [viewController didMoveToParentViewController:self];

Since the first two view controllers are loaded, there is no lag when the user swipes between them. When UIScrollView is scrolled, the page is scrolls to is already loaded. Once it does, I load the next one in order:

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
    CGFloat pageWidth = CGRectGetWidth(self.scrollView.frame);
    NSUInteger page = floor((self.scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;

    [self loadScrollViewWithPage:page - 1];
    [self loadScrollViewWithPage:page];
    [self loadScrollViewWithPage:page + 1];

This way you always have the current, previous and the next view controller always ready. You could load more than 3 if the network calls you have to make take longer and the what the user is expected to scroll at.

You could unload older view controllers by calling viewController.view = nil and always only have a set number of view controllers loaded.

I created a custom protocol for the UICollectionView and set my parent controller as delegate to respond to cell clicks and navigate to the appropriate page.

  • \$\begingroup\$ The documentation for unloading a view controller is here. \$\endgroup\$ Jul 29 '15 at 19:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.