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.
1 Answer 1
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.
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,
CGRectGetHeight(self.containerScrollView.frame))];
[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\$user79083– user790832015年07月29日 19:36:29 +00:00Commented Jul 29, 2015 at 19:36
Explore related questions
See similar questions with these tags.
UIPageViewController
. It does exactly what you're trying to accomplish manually. \$\endgroup\$