The first popover we're going to create is for
choosing the font used by the Text tool. We'll simply display a list of
all available font names on the device; specifying the size will be the
job of our next popover. What we need here is a view controller that
will let us display a list of selectable items, and this list may be
larger than the screen itself, so it should be scrollable—sounds like a
job for a UITableView!
Add a new class to your
project, and use the assistant that comes up to specify that you want a
Cocoa Touch class, specifically a UIViewController subclass. If you've ever created a UIViewController
subclass for iPhone in the past—and I suspect you have—this should look
pretty familiar. The main difference is the inclusion of a new Targeted
for iPad check box. If that's checked, Xcode will use a slightly
different template for creating your class. Go ahead and make sure
that's turned on, along with the UITableViewController subclass check box, but not the XIB check box, as shown in Figure 1. Click Next, and then enter FontListController as the name of your new class.
1. The Simplest Popover You'll Ever Create
Thanks to the power and flexibility of UITableView,
creating this class is going to be a breeze. All we need to do is add a
few instance variables for hanging onto a list of fonts as well as the
current selection, and fill in a few short methods in the controller
class.
Start with FontListController.h,
adding the instance variables and matching properties as shown in the
following code. We're also defining a string constant, which will be
used to let the main view controller know that the user has selected
something.
#import <UIKit/UIKit.h>
// we'll use a notification with this name, to let the main
// view controller know that something was selected here.
#define FontListControllerDidSelect @"FontListControllerDidSelect"
@interface FontListController : UITableViewController {
NSArray *fonts;
NSString *selectedFontName;
UIPopoverController *container;
}
@property (retain, nonatomic) NSArray *fonts;
@property (copy, nonatomic) NSString *selectedFontName;
@property (assign, nonatomic) UIPopoverController *container;
@end
As you can see, we also created an instance variable for pointing to the UIPopoverController that acts as the container for an instance of this class. FontListController doesn't have any use for this itself, but it will be used later when DudelViewController needs to close the containing UIPopoverController.
Now switch over to FontListController.m,
where we have a series of small changes to make to the default
template. Apart from the changes shown here, you can leave the rest of
the autogenerated class as is. First, synthesize all the declared
properties by adding this line inside the @implementation FontListController section:
@synthesize fonts, selectedFontName, container;
Then uncomment the viewDidLoad method, remove most of the code in there (except for the call to [super viewDidLoad]), and add the bold lines shown here to its body:
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *familyNames = [UIFont familyNames];
NSMutableArray *fontNames = [NSMutableArray array];
for (NSString *family in familyNames) {
[fontNames addObjectsFromArray:[UIFont fontNamesForFamilyName:family]];
}
self.fonts = [fontNames sortedArrayUsingSelector:@selector(compare:)];
}
In a nutshell, this
goes through an array of strings containing font family names, gets all
the fonts that belong to each family, and adds them to an array.
Finally, it puts them in alphabetical order and saves the sorted array
in the fonts instance variable.
Next, uncomment the viewWillAppear: method, and add the bold lines shown here:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSInteger fontIndex = [self.fonts indexOfObject:self.selectedFontName];
if (fontIndex != NSNotFound) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:fontIndex inSection:0];
[self.tableView scrollToRowAtIndexPath:indexPath
atScrollPosition:UITableViewScrollPositionMiddle animated:NO];
}
}
This code tries to find the
location of the selected font in the array of fonts, and scrolls the
table view to make it visible. This works under the assumption that the
code that initializes this class (which we'll add to DudelViewController soon) also sets the selectedFontName property. The check against NSNotFound
makes sure that we don't crash in case that value hasn't been set or is
set to something invalid (a font name that isn't in our list).
Next, we fill in the blanks for the basic UITableViewDatasource methods that every UITableViewController subclass must implement:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return [fonts count];
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
// Configure the cell...
NSString *fontName = [fonts objectAtIndex:indexPath.row];
cell.textLabel.text = fontName;
cell.textLabel.font = [UIFont fontWithName:fontName size:17.0];
if ([self.selectedFontName isEqual:fontName]) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
The first two methods are
self-explanatory, and the last one isn't much more complicated. It just
sees which font name is at the specified index, and uses that name both
as the display value and to look up a font. That way, each font name is
displayed in its own font! It also sets a check box on the cell if (and
only if) the current font name matches the selected font, so the user
can see the current selection while scrolling through the list.
Next, we're going to implement
the method that's called when the user selects a row. The idea is to
make a note of which font the user selected, update the display of the
affected rows (so the check box appears in the correct cell) to give the
user some immediate feedback, and then post a notification so that
whoever is listening, such as DudelViewController,
will get a chance to do something. This method already exists in the
template code, but contains some commented-out example code that isn't
relevant here. Delete that, and add the code shown in bold:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// determine two affected table columns: the one that was selected before,
// and the one that's selected now.
NSInteger previousFontIndex = [self.fonts indexOfObject:self.selectedFontName];
// don't do any updating etc. if the user touched the already-selected row
if (previousFontIndex != indexPath.row) {
NSArray *indexPaths = nil;
if (previousFontIndex!= NSNotFound) {
NSIndexPath *previousHighlightedIndexPath = [NSIndexPath
indexPathForRow:previousFontIndex inSection:0];
indexPaths = [NSArray arrayWithObjects:indexPath, previousHighlightedIndexPath, nil];
} else {
indexPaths = [NSArray arrayWithObjects:indexPath, nil];
}
// notice the new selection
self.selectedFontName = [self.fonts objectAtIndex:indexPath.row];
// then reload
[self.tableView reloadRowsAtIndexPaths:indexPaths
withRowAnimation:UITableViewRowAnimationFade];
[[NSNotificationCenter defaultCenter] postNotificationName:FontListControllerDidSelect
object:self];
}
}
Finally, we need to add a bit of cleanup, so that the list of font names doesn't hang around forever:
- (void)viewDidUnload {
// relinquish ownership of anything that can be re-created in viewDidLoad or on demand.
// For example: self.myOutlet = nil;
self.fonts = nil;
}
- (void)dealloc {
self.fonts = nil;
self.selectedFontName = nil;
[super dealloc];
}
That should be all we need for the FontListController
class itself. At this point, you should try to build your app, just to
make sure no syntax errors have snuck in, but you won't see any
difference when you run the app just yet. Our next step here will be
enabling DudelViewController to use our new class.