If you have ever tried to customize a UITableView though, you know that as soon as you start adding lots of UIViews, UILabels, and UImageViews to a cells ContentView, that these tableviews start to scroll slower and slower, and become choppier and choppier.
What we are going to explore today is how to remedy that situation.
To download the entire XCode project, you can find it at: http://github.com/elc/ICB_PrettyTableView
We are going to build a simple contact viewer, that will display the phones contacts. For each contact, if they have a first name, last name, email and phone number, they will be displayed within one cell, with different colors. The reason this is useful is because it provides the basics for customizing UITableViewCells that can really start to make your application look nice, and still scroll well.
If you don’t want to have simulated data inside the simulator, check out this post for copying data from your device to the simulator: How to import contacts into the iphone simulator
In this example, we have a standard UITableViewController. We are going to have a couple class variables defined in the header
#import#import @interface ICBTableViewController : UITableViewController { ABAddressBookRef _addressBook; } @property (nonatomic, retain) NSArray *contacts; @end
In the main table view controller file we are going to override the – (void)viewDidLoad to provide some initial configuration of the tableView, as well as loading or generating our data. (We will generate fake data for devices or the simulator that don’t have address book data)
- (void)viewDidLoad { [super viewDidLoad]; self.tableView.backgroundColor = UIColor.blackColor; self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; ABAddressBookRef addressBook = ABAddressBookCreate(); NSArray *tempArray = (NSArray *)ABAddressBookCopyArrayOfAllPeople(addressBook); tempArray = [tempArray sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { NSString *name1 = (NSString *)ABRecordCopyCompositeName((ABRecordRef)obj1); NSString *name2 = (NSString *)ABRecordCopyCompositeName((ABRecordRef)obj2); return [name1 compare:name2]; }]; if ([tempArray count] > 0) { self.contacts = tempArray; } else { NSMutableArray *tempMutableArray = [NSMutableArray arrayWithCapacity:100]; for (int i = 0; i < 100; ++i) { NSMutableDictionary *dict = [NSMutableDictionary dictionary]; if ((i % 9) != 0) { [dict setObject:[NSString stringWithFormat:@"FirstName%d", i] forKey:@"firstName"]; } if ((i % 3) == 0) { [dict setObject:[NSString stringWithFormat:@"LastName%d", i] forKey:@"lastName"]; } if ((i % 3) == 0 && (i % 2) == 0) { [dict setObject:[NSString stringWithFormat:@"emailTest%d@test%d.com", i, i] forKey:@"email"]; } if ((i % 7) == 0) { NSString *string = [NSString stringWithFormat:@"%d", i]; while ([string length] < 10) { string = [string stringByAppendingFormat:@"%@", string]; } [dict setObject:string forKey:@"phone"]; } [tempMutableArray addObject:dict]; } self.contacts = tempMutableArray; } }
If you are running this application on a device, or simulator that has contacts, this method will also make a copy of the address book as the data to display. If there is no data in the address book, we create some fake test data just for displaying.
Also don’t forget to include our – (void)dealloc method for releasing our _addressBook variable.
- (void)dealloc { CFRelease(_addressBook); [_contacts release]; _contacts = nil; }
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.contacts count]; }
// Customize the appearance of table view cells. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"ICBTableViewCellIdentifier"; ICBTableViewCell *cell = (ICBTableViewCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { cell = [[[ICBTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease]; cell.textLabel.textColor = UIColor.whiteColor; } cell.tag = indexPath.row; NSObject *object = [self.contacts objectAtIndex:indexPath.row]; if ([object isKindOfClass:NSDictionary.class]) { [cell setDictionary:(NSDictionary *)object]; } else { [cell setRecord:(ABRecordRef)object]; } return cell; }
Now the meat of this tutorial, extending a UITableViewCell.
In our header we are going to define a bunch of strings that we want to display
#import#import @interface ICBTableViewCell : UITableViewCell @property (nonatomic, retain) NSString *firstName; @property (nonatomic, retain) NSString *lastName; @property (nonatomic, retain) NSString *email; @property (nonatomic, retain) NSString *phone; @property (nonatomic, retain) NSString *address; - (void)setRecord:(ABRecordRef)record; - (void)setDictionary:(NSDictionary *)dict; @end
- (void)setRecord:(ABRecordRef)record { self.firstName = [(NSString *)ABRecordCopyValue(record, kABPersonFirstNameProperty) autorelease]; self.lastName = [(NSString *)ABRecordCopyValue(record, kABPersonLastNameProperty) autorelease]; self.email = [self getFirstEmail:record]; self.phone = [self getFirstPhone:record]; [self setNeedsDisplay]; } - (void)setDictionary:(NSDictionary *)dict { self.firstName = [dict objectForKey:@"firstName"]; self.lastName = [dict objectForKey:@"lastName"]; self.email = [dict objectForKey:@"email"]; self.phone = [dict objectForKey:@"phone"]; [self setNeedsDisplay]; }
The first thing I am doing is getting the current graphics context so that we can draw to the screen, clipping to the rect that is passed in drawRect:(CGRect)rect, and then depending on whether this cell is even, I am filling the entire rect with an almost black color, or slightly lighter.
- (void)drawRect:(CGRect)rect { CGContextRef ctx = UIGraphicsGetCurrentContext(); CGContextClipToRect(ctx, rect); //If even if (((self.tag % 2) == 0)) { CGContextSetFillColorWithColor(ctx, [UIColor colorWithWhite:0.1f alpha:1.f].CGColor); } else { CGContextSetFillColorWithColor(ctx, [UIColor colorWithWhite:0.15f alpha:1.f].CGColor); } CGContextFillRect(ctx, rect);
//Vertically center our text, if no email BOOL isCentered = (self.email == nil);
CGRect tempRect;
CGFloat midY = CGRectGetMidY(rect);
[[UIColor whiteColor] set];
UIFont *defaultFont = [UIFont systemFontOfSize:16];
CGSize size = [self.firstName sizeWithFont:defaultFont];
if (isCentered == NO) {
tempRect = CGRectMake(5, 0, size.width, size.height);
} else {
tempRect = CGRectMake(5, midY - size.height/2, size.width, size.height);
}
[self.firstName drawInRect:tempRect withFont:defaultFont];
[[UIColor lightGrayColor] set];
size = [self.lastName sizeWithFont:defaultFont];
if (isCentered == NO) {
tempRect = CGRectMake(CGRectGetMaxX(tempRect)+5, 0, size.width, size.height);
} else {
tempRect = CGRectMake(CGRectGetMaxX(tempRect)+5, midY - size.height/2, size.width, size.height);
}
[self.lastName drawInRect:tempRect withFont:defaultFont];if (self.phone != nil) { [[UIColor redColor] set]; size = [self.phone sizeWithFont:defaultFont]; CGFloat end = CGRectGetMaxX(tempRect) + size.width; if (end > rect.size.width) { size.width = CGRectGetMaxX(rect) - CGRectGetMaxX(tempRect) - 10; //-10 so that we get 5 from the end of last name, and 5 from the end of rect } if (isCentered == NO) { tempRect = CGRectMake(CGRectGetMaxX(rect) - size.width - 5, 0, size.width, size.height); } else { tempRect = CGRectMake(CGRectGetMaxX(rect) - size.width - 5, midY - size.height/2, size.width, size.height); } [self.phone drawInRect:tempRect withFont:defaultFont lineBreakMode:UILineBreakModeTailTruncation]; }
if (self.email != nil) { [[UIColor blueColor] set]; size = [self.email sizeWithFont:defaultFont]; tempRect = CGRectMake(5, midY, size.width, size.height); [self.email drawInRect:tempRect withFont:defaultFont]; }
To download the entire XCode project, you can find it at: http://github.com/elc/ICB_PrettyTableView
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.