I want to build a train arrival time reference app, that lets me choose different stations and displays times from A to B, and times from B to A. So I'm using Apple's Page Control because I want to have multiple pages for each station, and each page would list all the train times. Here's my appdelegate:
#import "AppDelegate.h"
#import "MyViewController.h"
static NSUInteger kNumberOfPages = 2;
@interface AppDelegate (PrivateMethods)
- (void)loadScrollViewWithPage:(int)page;
- (void)scrollViewDidScroll:(UIScrollView *)sender;
@end
@implementation AppDelegate
@synthesize window, scrollView, pageControl, viewControllers;
- (void)dealloc {
[viewControllers release];
[scrollView release];
[pageControl release];
[window release];
[super dealloc];
}
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// view controllers are created lazily
// in the meantime, load the array with placeholders which will be replaced on demand
NSMutableArray *controllers = [[NSMutableArray alloc] init];
for (unsigned i = 0; i < kNumberOfPages; i++) {
[controllers addObject:[NSNull null]];
}
self.viewControllers = controllers;
[controllers release];
// a page is the width of the scroll view
scrollView.pagingEnabled = YES;
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * kNumberOfPages, scrollView.frame.size.height);
scrollView.showsHorizontalScrollIndicator = NO;
scrollView.showsVerticalScrollIndicator = NO;
scrollView.scrollsToTop = NO;
scrollView.delegate = self;
pageControl.numberOfPages = kNumberOfPages;
pageControl.currentPage = 0;
// pages are created on demand
// load the visible page
// load the page on either side to avoid flashes when the user starts scrolling
[self loadScrollViewWithPage:0];
[self loadScrollViewWithPage:1];
}
- (void)loadScrollViewWithPage:(int)page {
if (page < 0) return;
if (page >= kNumberOfPages) return;
// replace the placeholder if necessary
MyViewController *controller = [viewControllers objectAtIndex:page];
if ((NSNull *)controller == [NSNull null]) {
controller = [[MyViewController alloc] initWithPageNumber:page];
[viewControllers replaceObjectAtIndex:page withObject:controller];
[controller release];
}
// add the controller's view to the scroll view
if (nil == controller.view.superview) {
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
controller.view.frame = frame;
[scrollView addSubview:controller.view];
}
}
- (void)scrollViewDidScroll:(UIScrollView *)sender {
// We don't want a "feedback loop" between the UIPageControl and the scroll delegate in
// which a scroll event generated from the user hitting the page control triggers updates from
// the delegate method. We use a boolean to disable the delegate logic when the page control is used.
if (pageControlUsed) {
// do nothing - the scroll was initiated from the page control, not the user dragging
return;
}
// Switch the indicator when more than 50% of the previous/next page is visible
CGFloat pageWidth = scrollView.frame.size.width;
int page = floor((scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
pageControl.currentPage = page;
// load the visible page and the page on either side of it (to avoid flashes when the user starts scrolling)
[self loadScrollViewWithPage:page - 1];
[self loadScrollViewWithPage:page];
[self loadScrollViewWithPage:page + 1];
// A possible optimization would be to unload the views+controllers which are no longer visible
}
// At the begin of scroll dragging, reset the boolean used when scrolls originate from the UIPageControl
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
pageControlUsed = NO;
}
// At the end of scroll animation, reset the boolean used when scrolls originate from the UIPageControl
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
pageControlUsed = NO;
}
- (IBAction)changePage:(id)sender {
int page = pageControl.currentPage;
// load the visible page and the page on either side of it (to avoid flashes when the user starts scrolling)
[self loadScrollViewWithPage:page - 1];
[self loadScrollViewWithPage:page];
[self loadScrollViewWithPage:page + 1];
// update the scroll view to the appropriate page
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
[scrollView scrollRectToVisible:frame animated:YES];
// Set the boolean used when scrolls originate from the UIPageControl. See scrollViewDidScroll: above.
pageControlUsed = YES;
}
@end
The app delegate loads up "MyViewController". What I'm trying to do with my view controller, is have two separate normal tables with different sets of information. I was able to achieve this.
I then modified the two tables to have custom cells consisting of two labels that I want to be able to set. Here is MyViewControler:
#import "MyViewController.h"
#import "CustomCell.h"
static NSArray *__pageControlColorList = nil;
static NSArray *__stationNameList = nil;
@implementation MyViewController
@synthesize stationName;
@synthesize homeBound;
@synthesize workBound;
@synthesize homeBoundTimes;
@synthesize workBoundTimes;
@synthesize Custom;
// Creates the color list the first time this method is invoked. Returns one color object from the list.
+ (UIColor *)pageControlColorWithIndex:(NSUInteger)index {
if (__pageControlColorList == nil) {
__pageControlColorList = [[NSArray alloc] initWithObjects:[UIColor colorWithRed:16.0/255 green:102.0/255 blue:73.0/255 alpha:1.0],
[UIColor colorWithRed:248.0/255 green:155.0/255 blue:70.0/255 alpha:1.0], nil];
}
// Mod the index by the list length to ensure access remains in bounds.
return [__pageControlColorList objectAtIndex:index % [__pageControlColorList count]];
}
//Creates the station list the first time this method is invoked. Returns one string object from the list.
+ (NSString *)pageControlStationWithIndex:(NSUInteger)index {
if (__stationNameList == nil) {
__stationNameList = [[NSArray alloc] initWithObjects:[NSString stringWithFormat:@"Bramalea"], [NSString stringWithFormat:@"Erindale"], nil];
}
//Mod the index by the list length to ensure access remains in bounds.
return [__stationNameList objectAtIndex:index % [__stationNameList count]];
}
//Creates the station list the first time this method is invoked. Returns one string object from the list.
+ (NSArray *)HomeTimesByIndex:(NSUInteger)index {
if (index == 0) {
return [[NSArray alloc] initWithObjects:@"5:55 am", @"6:49 am", @"7:14 am", @"7:30 am", @"7:38 am",
@"8:07 am", @"9:20 am", @"10:25 am", @"11:06 am", @"12:15 pm",
@"1:45 pm", @"2:51 pm", @"3:51 pm", @"4:51 pm", @"5:51 pm",
@"6:51 pm", @"7:21 pm", @"7:51 pm", @"8:51 pm", @"9:51 pm", nil];
} else {
return [[NSArray alloc] initWithObjects:@"5:10 am", @"5:45 am", @"6:45 am", @"7:10 am",
@"7:30 am", @"7:45 am", @"8:00 am", @"8:15 am",
@"8:30 am", @"9:05 am", @"9:40 am", @"10:15 am",
@"10:45 am", @"11:15 am", @"11:45 am", @"12:15 pm",
@"12:45 pm", @"1:15 pm", @"1:45 pm", @"2:15 pm", @"2:45 pm",
@"3:15 pm", @"3:45 pm", @"4:15 pm", @"5:20 pm", @"6:20 pm",
@"7:15 pm", @"8:15 pm", @"9:15 pm", @"10:15 pm", @"11:15 pm", nil];
}
}
+ (NSArray *)WorkTimesByIndex:(NSUInteger)index {
if (index == 0) {
return [[NSArray alloc] initWithObjects:@"6:25 am", @"7:30 am", @"8:30 am", @"9:40 am", @"11:30 am", @"1:00 pm", @"1:50 pm",
@"2:00 pm", @"2:45 pm", @"4:15 pm", @"4:45 pm", @"5:15 pm", @"5:45 pm", @"6:45 pm",
@"7:20 pm", @"7:35 pm", @"7:55 pm", @"8:15 pm", @"8:35 pm", @"9:00 pm", @"9:30 pm",
@"10:00 pm", @"10:30 pm", @"11:00 pm", @"11:30 pm", @"12:01 am", @"12:30 am", @"1:30 am", nil];
} else {
return [[NSArray alloc] initWithObjects:
@"6:20 am", @"7:20 am", @"8:20 am", @"9:20 am", @"10:20 am", @"11:20 am", @"12:20 pm",
@"12:50 pm", @"1:20 pm", @"1:40 pm", @"2:00 pm", @"2:20 pm", @"2:40 pm", @"2:55 pm",
@"3:10 pm", @"3:25 pm", @"4:30 pm", @"4:50 pm", @"5:10 pm", @"5:25 pm", @"5:40 pm",
@"6:10 pm", @"7:00 pm", @"7:25 pm", @"7:40 pm", @"7:55 pm", @"8:10 pm", @"8:30 pm",
@"8:50 pm", @"9:10 pm", @"9:30 pm", @"9:50 pm", @"10:20 pm", @"11:20 pm", @"12:20 am", @"1:20 am", nil];
}
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
CGRect frame = self.view.frame;
frame.size.height = 600;
self.view.frame = frame;
}
// Load the view nib and initialize the pageNumber ivar.
- (id)initWithPageNumber:(int)page {
if (self = [super initWithNibName:@"Station" bundle:nil]) {
pageNumber = page;
}
return self;
}
- (void)dealloc {
[stationName release];
[homeBound release];
[workBound release];
[homeBoundTimes release];
[workBoundTimes release];
[Custom release];
[super dealloc];
}
// Set the label and background color when the view has finished loading.
- (void)viewDidLoad {
//Get station name and set station texts
stationName.text = [MyViewController pageControlStationWithIndex:pageNumber];
homeBound.text = [NSString stringWithFormat:@"Next Train From Union to %@:", [MyViewController pageControlStationWithIndex:pageNumber]];
workBound.text = [NSString stringWithFormat:@"Next Train From %@ to Union:", [MyViewController pageControlStationWithIndex:pageNumber]];
self.view.backgroundColor = [MyViewController pageControlColorWithIndex:pageNumber];
NSArray *arrayHome = [MyViewController HomeTimesByIndex:pageNumber];
NSArray *arrayWork = [MyViewController WorkTimesByIndex:pageNumber];
self.homeBoundTimes = arrayHome;
self.workBoundTimes = arrayWork;
[arrayHome release];
[arrayWork release];
}
#pragma mark -
#pragma mark Table View Data Source Methods
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
if ([tableView tag] == 0) {
return [self.homeBoundTimes count];
} else if ([tableView tag] == 1) {
return [self.workBoundTimes count];
}
return 0;
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CustomCellIdentifier = @"CustomCellIdentifier";
CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:CustomCellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:@"CustomCell" owner:self options:nil];
cell = self.Custom;
}
NSUInteger row = [indexPath row];
if ([tableView tag] == 0) {
cell.arriveTimeLabel.text = [homeBoundTimes objectAtIndex:row];
cell.departTimeLabel.text = [homeBoundTimes objectAtIndex:row];
} else if ([tableView tag] == 1) {
cell.arriveTimeLabel.text = [workBoundTimes objectAtIndex:row];
cell.departTimeLabel.text = [workBoundTimes objectAtIndex:row];
}
return cell;
}
@end
The values to be loaded into the table are hardcoded for now (experimenting with the db next).
My problem, is that when the table is viewed, only the first row has data, and the rest of the data "sorta" loads when I start scrolling down. By "sorta", only the first row will load at a time. The only time I get to see all the data, is when I scroll to the bottom, then scroll up again or when I select a row. But the information doesn't stay at all times. Sometimes when I scroll, I will lose the information until I scroll over that particular row again.
Does anyone have any ideas why this is happening?
Edit: Jim's answer did the trick. Here's my modified method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:@"CustomCellIdentifier"];
if (cell == nil) {
// Load the top-level objects from the custom cell XIB.
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"CustomCell" owner:self options:nil];
// Grab a pointer to the first object (presumably the custom cell, as that's all the XIB should contain).
cell = [topLevelObjects objectAtIndex:0];
NSUInteger row = [indexPath row];
if ([tableView tag] == 0) {
cell.arriveTimeLabel.text = [homeBoundTimes objectAtIndex:row];
cell.departTimeLabel.text = [homeBoundTimes objectAtIndex:row];
} else if ([tableView tag] == 1) {
cell.arriveTimeLabel.text = [workBoundTimes objectAtIndex:row];
cell.departTimeLabel.text = [workBoundTimes objectAtIndex:row];
}
}
return cell;
}