Blog

UIImage, resolution independence and the iPhone 4′s Retina display

iOS4 caters for the high-resolution Retina display that comes with the iPhone 4 by some rather clever abstraction, that moves away from the concept of ‘pixels’, and instead uses ‘points’, which are resolution-independent.

So, when you display an image that’s been prepared for the Retina display, it’s represented with a scale factor of 2, meaning that to your code, it appears to have the same dimensions, but in fact contains twice the information density.

iOS4′s UIImage makes it work by automatically looking for high-res images located alongside the prior ‘standard resolution’ ones — identified by a “@2x” suffix to the filename.

This works great with +[UIImage imageNamed:], but although the API documentation says that other image loading methods will automatically load the @2x versions, they actually don’t. Yeah. Apple are working on it.

Until they sort themselves out, I’m using a convenience method sitting inside a UIImage category. So, where I would previously use something like [UIImage imageWithContentsOfFile:], I now use [UIImage imageWithContentsOfResolutionIndependentFile:].

Here’s the category:

@interface UIImage (TPAdditions)
- (id)initWithContentsOfResolutionIndependentFile:(NSString *)path;
+ (UIImage*)imageWithContentsOfResolutionIndependentFile:(NSString *)path;
@end
 
 
@implementation UIImage (TPAdditions)
 
- (id)initWithContentsOfResolutionIndependentFile:(NSString *)path {
    if ( [[[UIDevice currentDevice] systemVersion] intValue] >= 4 && [[UIScreen mainScreen] scale] == 2.0 ) {
        NSString *path2x = [[path stringByDeletingLastPathComponent] 
                            stringByAppendingPathComponent:[NSString stringWithFormat:@"%@@2x.%@", 
                                                            [[path lastPathComponent] stringByDeletingPathExtension], 
                                                            [path pathExtension]]];
 
        if ( [[NSFileManager defaultManager] fileExistsAtPath:path2x] ) {
            return [self initWithCGImage:[[UIImage imageWithData:[NSData dataWithContentsOfFile:path2x]] CGImage] scale:2.0 orientation:UIImageOrientationUp];
        }
    }
 
    return [self initWithData:[NSData dataWithContentsOfFile:path]];
}
 
+ (UIImage*)imageWithContentsOfResolutionIndependentFile:(NSString *)path {
    return [[[UIImage alloc] initWithContentsOfResolutionIndependentFile:path] autorelease];
}
 
@end

This checks to see whether the iOS4 features are present, so this code should work on devices running prior OS versions as well.

What iOS4′s imageWithContentsOfFile does do is recognise if an image is the ’2x’ version, and sets the scale accordingly, so it displays correctly.

Update: I just discovered that -[UIDevice scale] actually exists prior to iOS 4. Apple recommend using tests like respondsToSelector to determine OS features, rather than version checks, but in this case, it gives the wrong result that can result in some pretty hard to find bugs. I’ve updated the above class to check the iOS version instead.

I’ve also avoided using initWithContentsOfFile entirely: As Tea mentioned in a comment below, we probably shouldn’t be trusting this method any more, as it does not behave as advertised.

Update 2: I’ve been unable to replicate the problem, but a few commenters below noted issues with the 2x image being displayed at double-size. I’ve now updated the above to explicitly set the scale to 2.0 for the 2x image, which should fix the problem.

Before and After resolution independence

Tagged , , . Bookmark the permalink. Both comments and trackbacks are currently closed.

28 Comments

  1. Funbit
    Posted June 29, 2010 at 3:37 am | Permalink

    Who does like that? I spent 2 hours trying to find out why my images aren’t loaded automatically via “imageWithContentsOfFile”. And actually, it shouldn’t, because feature isn’t implemented. But mentioned in the official docs. Stupid SDK.

    And thanks for saving another hour..

  2. Marjan
    Posted June 29, 2010 at 2:59 pm | Permalink

    I am currently doing first steps on this, and fail miserably…

    for example the only way to get a HD Icon to show up was to put this in info.plist:

    Icon.png Icon@x2.png

    where Icon@x2.png MUST be the last entry, otherwise iPhone4 will show the other one.

    And if i only add to startupscreens in my project “Default@x2.png” and “Default.png” iPhone4 will not display the hd image.

    for that to show up i need to add to Info.plist: UILaunchImageFile Default@x2.png

    Then iPhone4 has hd and older ones the low res Startscreen.

    Is that the way to do it??

    What about Interfacebuilder? If i use UIImage or Buttons there, they actually also don´t show the HD versions, although a have every image there 2 times, correctly named.

    I must be missing something really basic!

    BUT WHAT??

  3. Marjan
    Posted June 29, 2010 at 4:06 pm | Permalink

    Forget it… Silly me named everything @x2 instead of @2x. Dooooaaahhh

    • Posted June 29, 2010 at 4:24 pm | Permalink

      Ah, that was an easy fix ;-)

  4. Bryan
    Posted July 3, 2010 at 12:06 am | Permalink

    Any word from Apple on when imageWithContentsOfFile: will correctly find and open @2x images?

  5. Mike
    Posted July 5, 2010 at 9:14 pm | Permalink

    Hi, This method of yours don’t work if we have images like “myImage~iphone.png”, “myImage@2x~iphone.png” and “myImage~ipad.png”, because the @2x is put at the end.

    I have modified your method to add the @2x at the right place (I’ll not post here because the code inside this comment will be destroyed. If you want, please drop me a note and I will email you the code, so you can post it here).

    But even fixed, my code doesn’t load the ~ipad files… and I don’t know why… probably another bug of the SDK.

  6. Posted July 11, 2010 at 5:28 am | Permalink

    Ugh!! This took, like, 2 hours of my time before I finally found your post. Thank you so much! I hope to be able to remove this categorical extension sometime soon, though… I’ve no idea how I’ll find out about their fix, though; I kinda doubt they’ll note “Made ‘initWithContentsOfFile’ actually work” in the official documentation.

    Looks like I’ll be manually checking every time they update iOS ;)

  7. Posted July 15, 2010 at 9:52 pm | Permalink

    This is failing for me on iPad. I’m working on an iPhone/iPod Touch app and testing on an iPad to be thorough… It turns out that OS 3.2 UIScreen does respond to “Scale” (when you click the 1x/2x button that changes the scale, I think). So on iPad my app shows images that are GIANT (i.e. The @2x images).

    • Marjan
      Posted July 16, 2010 at 9:34 am | Permalink

      Thats because the iPad has iOs 3.2, not 4. and this scale thing is an iOs 4 thing. You have to ifthen the according things out for lower ios4 devices and use a variable with value 1 instead.

      iPad has a scale 1 anyways, maybe in the future there will be a hD iPad with maybe 2048 by 1536 Pixels. with iOs 4 such a divece will likly hafe a scalefactor of 2. And only then you could use images like myimage@2x~ipad.png.

      So far with iOs 3.2 even the naming myimage~ipad.png for automatic loading of iPad optimized grafics doesn´t work. I guess thats also iOs 4 only.

  8. Scott Gress
    Posted July 20, 2010 at 10:43 pm | Permalink

    Thanks for this code! It works great on the iPhone. However, I’m having trouble when compiling against OS 3.2 for the iPad. It seems that the new method initWithContentsOfResolutionIndependentFile isn’t added to the class, so that the program dies whenever the method is called, saying that UIImage doesn’t respond to that selector. Any ideas on why this might be?

    • Posted July 26, 2010 at 8:54 pm | Permalink

      This’ll be the same problem as Eric’s above – 3.2 doesn’t actually support resolution independence, I’m afraid

  9. Posted July 29, 2010 at 9:16 pm | Permalink

    I’ve noticed that using imageWithContentsOfFile does load the high res version of my image (I put a marker on it), but when I check the scale property of the UIImage it still says “1.0″. Anyone else have this behavior?

    • Posted August 29, 2010 at 8:36 pm | Permalink

      With imageWithContentsOfFile it doesn’t seem there is a consistent way to ensure which image will load. With only the hi-res image in resources, I get the hi-res image, with scale=1.0 as you noticed (and the image appears ‘double size’). With both the hi-res and low-res version I get the low-res version displaying on my iPhone4 :( As of now, imageWithContentsOfFile is unsafe. No matter what the current behavior is now, I doubt we can rely on it.

  10. Alan Taylor
    Posted August 18, 2010 at 5:33 pm | Permalink

    I’m a little confused about how to use this code – where should we put this to be able to successfully call it?

    Thanks!

    • Posted August 18, 2010 at 9:29 pm | Permalink

      Put the @interface section in a file called, say, UIImage+TPAdditions.h, and the @implementation section in a corresponding UIImage+TPAdditions.m file, with #include “UIImage+TPAdditions.h” at the top; include the .h file the same way at the top of any other source file you want to use it in, and call [UIImage imageWithContentsOfResolutionIndependentFile:yourFilePath].

  11. Posted August 29, 2010 at 8:29 pm | Permalink

    Nice one! I had a go at imageWithContentsOfFile before checking this post, so finding this saved me time :)

  12. Posted September 9, 2010 at 5:50 pm | Permalink

    I’ve just posted an update to this entry, after discovering the cause of a very confusing bug: It turns out, -[UIDevice scale] exists on iOS versions prior to 4, thereby throwing off my compatibility checks. Bad Apple.

  13. Posted September 14, 2010 at 10:07 pm | Permalink

    Michael, awesome site man, very useful post. Did you try if the imageWithContentsOfFile works with the 4.1 ? Anyhow … as said above we can’t trust the method anymore anyway. Awesome site, I’ll stick around :) Cheers, Marin

  14. Posted September 18, 2010 at 12:34 am | Permalink

    @Michael – do you have a solution for the problem Adam and Tea are talking about? your category correctly finds out which version of the image to load, but the @2x images are loaded with scale factor set to 1.0 thus they appear 2 times bigger than they should – I’ve got this problem too …

    • Posted September 18, 2010 at 1:31 am | Permalink

      Out of curiosity, Marin, what version of iOS does this happen in? I’ve been unable to replicate the problem in the simulator!

      One thing to try though: Instead of:

      return [self initWithData:[NSData dataWithContentsOfFile:path2x]];

      Try:

      return [self initWithCGImage:[[UIImage imageWithData:[NSData dataWithContentsOfFile:path2x]] CGImage] scale:2.0 orientation:UIImageOrientationUp];

      If you do give it a go, I’d love to hear if it works for you – I’m getting an iPhone 4, but until then, it’s a guessing-game for me. If it works, I’ll update the code above.

  15. Posted September 18, 2010 at 11:25 am | Permalink

    Oh yes! Awesome idea … I forgot CGImage actually takes a scale factor as parameter ! Great Job Michael this solves the problem.

    I’m testing on iPhone 4 Simulator with 4.0.2 SDK, don’t have the real device myself either

    • Posted September 18, 2010 at 5:09 pm | Permalink

      Glad to hear it – I’ve updated the code.

  16. Brian Gibson
    Posted October 4, 2010 at 6:09 pm | Permalink

    Code for iPad. Should default to 1x version for iPad 3.2 and presumably when 4.2 comes out, iPad will support initWithCGImage:scale:orientation:

    if ([UIImage instancesRespondToSelector:@selector(imageWithData:scale:orientation:)]) {
        return [self initWithCGImage:[[UIImage imageWithData:[NSData dataWithContentsOfFile:path2x]] CGImage] scale:2.0 orientation:UIImageOrientationUp];
    } else {
        return [self initWithContentsOfFile:path];
    }
    

  17. Brian Gibson
    Posted October 4, 2010 at 6:39 pm | Permalink

    Whoops! there’s a typo in my if-condition. it should be:

    [UIImage respondsToSelector:@selector(initWithCGImage:scale:orientation:)])
     

    • Posted October 5, 2010 at 11:54 am | Permalink

      Cheers, Brian. One thought: That always sets the scale to 2x, even if [UIScreen scale] actually reports something different. If you’re running at 1x, then everything’s going to look wrong, unless I’ve missed something.

  18. Posted November 3, 2010 at 10:00 am | Permalink

    I’m having a hard time following the instructions, I find it too confusing. BTW, though I’m having a hard time with this, I still want to thank you for your contribution. Keep up the good work Michael!

  19. Posted November 25, 2011 at 4:05 pm | Permalink

    Hi, I’m working on a Universal project at moment (iOS4.2+) with lots of images. I have just discovered that +imageWithContentsOfFile seems to be ignoring the presence of iPhone files (ie myImage@2x~iphone.png as opposed to myImage.png) and instead on an iPhone is simply loading the iPad file and scaling it… (!)

    So my call is like this:

    • (UIImage *)imageFromMainBundleFile:(NSString *)aFileName { NSString *path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:aFileName]; return [UIImage imageWithContentsOfFile:path]; }

    And then that is used like:

    [UIImage imageFromMainBundleFile:@"Assets/General/Map/myImage.png"];

    In that folder are 3 image files:

    myImage.png // ipad myImage~iphone.png // iPhone standard myImage@2x~iphone.png // iPhone Retina

    According to the documentation I am lead to believe that imageWithContentsOfFile should just “do the right thing” with names. However, it also ignores ~ipad as part of the name too, and now I am finding it is ignoring the presence of ~iphone files.

    Just wondered if you had noticed this and perhaps can see what I am doing to cause the iPhone to be loading the iPad image (tested on device running iOS 5.01) :) Thanks, Mike

    • Posted January 8, 2012 at 12:13 pm | Permalink

      Good lord, I’m sorry about the massive delay, Mike – I’ve been drowning in blog comments, and am only just getting a handle on the backlog.

      Yeah, that doesn’t surprise me at all – Apple screwed the pooch with the UIImage stuff, which is precisely the reason I wrote this blog entry, and the category which correctly implements the ’2x’ functionality =)

3 Trackbacks

  1. [...] which addresses this particular problem, greatly inspired by Michael Tyson’s blogpost: “UIImage, resolution independence and the iPhone 4′s Retina display” . It actually overrides the initWithContentsOfFile method, checks if the scaling of the [...]

  2. By Universal launch images with PhoneGap on October 1, 2010 at 10:39 pm

    [...] apparently doesn't correctly pick up the @2x resources for the iPhone 4 retina [...]

  3. [...] UIImage, resolution independence and the iPhone 4′s Retina display Share this:TwitterFacebookLike this:LikeBe the first to like this post. [...]