What is the right pattern for instantiating a device specific view controller in a Universal App?
I'm new to objective-C so, bear with me. I started with the Universal App template in Xcode4 and b开发者_如何学Pythonuilt my application. There is a convention that the template starts you off with that I tried to stick with. For each View Controller, there's a main file and a subclass for each device type. For example:
Project/
ExampleViewController.(h|m)
- iPhone/
- ExampleViewController_iPhone.(h|m|xib)
- iPad/
- ExampleViewController_iPad.(h|m|xib)
For the most part, this is pretty convenient. Most of the logic goes in the superclass and the subclasses take care of any device specific implementation.
Here's the part I don't get. Sometimes I have code that does the same thing in each subclass simply because I need to load a different xib for each device. For example:
ExampleViewController_iPhone
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
Content *selectedContent = (Content *)[[self fetchedResultsController] objectAtIndexPath:indexPath];
ContentDetailViewController_iPhone *detailViewController = [[ContentDetailViewController_iPhone alloc] init];
detailViewController.content = selectedContent;
detailViewController.managedObjectContext = self.managedObjectContext;
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
}
ExampleViewController_iPad
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
Content *selectedContent = (Content *)[[self fetchedResultsController] objectAtIndexPath:indexPath];
ContentDetailViewController_iPad *detailViewController = [[ContentDetailViewController_iPad alloc] init];
detailViewController.content = selectedContent;
detailViewController.managedObjectContext = self.managedObjectContext;
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
}
... notice that in the second instance the only thing different is that it's loading the _iPad
version of the View Controller. This is necessary because the iPad
and iPhone
view controllers are attached to separate, device specifc nibs.
What's the "correct" pattern for doing this?
UPDATE
I found this answer about loading separate xibs using device modifiers which seems like it could help in the case where I don't need a specific subclass for one device, but it still won't help if I need to instantiate a specific _iPhone
or _iPad
instance of a view controller for device specific functionality.
There are two simple ways to solve your problem. Both will work when placed in your superclass.
The first method only works because you have two different classes, and which one is created depends on the device being used. It does not work if you don't use different classes because there is no device-specific code. It involves asking the object for its class to decide which it is. Since the object's class will be the device-specific class, even when asked for by the superclass, you can check to see which class was created and act accordingly.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
Content *selectedContent = (Content *)[[self fetchedResultsController] objectAtIndexPath:indexPath];
Class classToUse;
if([self class] == [ExampleViewController_iPad class]) {
// We are running on the iPad
classToUse = [ContentDetailViewController_iPad class];
} else {
// We must be running on either the iPhone or iPod touch
classToUse = [ContentDetailViewController_iPhone class];
}
// Just use superclass for typing here, since we aren't doing anything device specific
ContentDetailViewController *detailViewController = [[classToUse alloc] init];
detailViewController.content = selectedContent;
detailViewController.managedObjectContext = self.managedObjectContext;
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
}
The second method will work in any location in your program. Apple added a property to the UIDevice class in iOS 3.2 called the "user interface idiom". There are currently two possible values: UIUserInterfaceIdiomPad
and UIUserInterfaceIdiomPhone
. Since these don't exist in versions prior to 3.2, Apple also added a macro which will return UIUserInterfaceIdiomPhone
if the version is less than 3.2, and get the actual value from the UIDevice object if it is greater than or equal to 3.2.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
Content *selectedContent = (Content *)[[self fetchedResultsController] objectAtIndexPath:indexPath];
Class classToUse;
// If you aren't supporting versions prior to 3.2, you can use [UIDevice currentDevice].userInterfaceIdiom instead to save a couple of cycles
if(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
// We are running on the iPad
classToUse = [ContentDetailViewController_iPad class];
} else {
// We must be running on either the iPhone or iPod touch
classToUse = [ContentDetailViewController_iPhone class];
}
// Just use superclass for typing here, since we aren't doing anything device specific
ContentDetailViewController *detailViewController = [[classToUse alloc] init];
detailViewController.content = selectedContent;
detailViewController.managedObjectContext = self.managedObjectContext;
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
}
精彩评论