Selecting images in Universal apps

Developing for the iPhone platform is getting more complicated as the differences in capabilities between the various iPhone devices increases. Prior to the introduction of the iPad one thing that you could rely on was that the screen dimensions and resolution was always the same.

The introduction of the iPad with its much larger screen means that any images or icons designed for the iPhone either look too small or less pleasing when scaled up. Creating multiple versions of an image is fine but it means introducing a lot of conditional code like the following:

if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {    
  UIImage *myImage = [UIImage imageNamed:@"myImage-iPad.png"];
  self.imageView.image = myImage;
} else {
  UIImage *myImage = [UIImage imageNamed:@"myImage.png"];
  self.imageView.image = myImage;
}

This is ok once or twice but I think it is better to isolate the platform dependent behaviour into one place in the code. Apple has an elegant solution for specifying device specific resources such as the application icon in the Info.plist file. For example, an iPad specific icon can be specified as Icon-iPad.png and the OS takes care of deciding when to use it in place of the default iPhone specific Icon.png. I would like to try to do something similar for loading UIImage files.

Adding a category to UIImage

The temptation to add new methods to existing UIKit classes is very strong so it is easy to get carried away. However I think in this case it does simplify the code so is the approach I have chosen to take. First I defined a new category called ScaledImage on UIImage with a single method:

@interface UIImage (ScaledImage)
+ (UIImage *)scaledImageNamed:(NSString *)name;
@end

A consideration you need to make here is to choose names that Apple or other framework developers are not likely to introduce in a future version of the framework. One way to do that is to prefix the category and all its methods with a code (e.g. your initials). So my method would end up being named something like ABC_scaledImageNamed. For clarity I won’t do that here but you should probably consider doing something similar if you follow this approach.

The definition of the category (in file UIImage+ScaledImage.m) is as follows:

#import <UIKit/UIKit.h>
#import "UIImage+ScaledImage.h"

@implementation UIImage (ScaledImage)

+ (UIImage *)scaledImageNamed:(NSString *)name {
  UIImage *image = nil;
  
  if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
    // Seperate the string name into a path and extension
    NSString *path = [name stringByDeletingPathExtension];
    NSString *extension = [name pathExtension];
    
    // Build the iPad version of the filename
    // <path>-iPad.<extension>
    NSString *nameipad = [NSString stringWithFormat:@"%@-iPad.%@", 
                                                    path, extension];
    image = [UIImage imageNamed:nameipad];
  } 
  
  // Fallback to the standard version of the image
  if (!image) {
    image = [UIImage imageNamed:name];
  }

  return image;
}
@end

To use the new method import the header file UIImage+ScaledImage.h and replace calls to (UIImage *)imageNamed: with the (UIImage *)scaledImageNamed: method. Then if I have an image file named myImage.png in my iPhone project when I create the Universal version of the app I can create the new, larger version of the image for the iPad and name the file myImage-iPad.png. The code to load the image would however remain unchanged:

UIImage *myImage = [UIImage scaledImageNamed:@"myImage.png"];
self.imageView.image = myImage;

Future Devices and Screen Resolutions

It is a poorly kept secret that Apple will introduce new iPhone devices this summer, most likely at WWDC in June. If the reports and rumours turn out to be true these new devices will have higher resolution displays even if they are physically the same size as existing devices. If so it may be necessary to introduce a third set of icons for this higher resolution. Moving the code to make this decision to a single place now should make life easier later.