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](http://developer.apple.com/iphone/library/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/SupportingResolutionIndependence/SupportingResolutionIndependence.html#//apple_ref/doc/uid/TP40007072-CH10-SW8) 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.
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..
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 [email protected]
where [email protected] MUST be the last entry, otherwise iPhone4 will show the other one.
And if i only add to startupscreens in my project “[email protected]” and “Default.png” iPhone4 will not display the hd image.
for that to show up i need to add to Info.plist: UILaunchImageFile [email protected]
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??
Forget it… Silly me named everything @x2 instead of @2x. Dooooaaahhh
Ah, that was an easy fix ;-)
Any word from Apple on when imageWithContentsOfFile: will correctly find and open @2x images?
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.
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 ;)
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).
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.
Pingback: Custom UIImage Subclass To Support @2x Retina Devices | Randomize Everything
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?
This’ll be the same problem as Eric’s above – 3.2 doesn’t actually support resolution independence, I’m afraid
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?
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.
I’m a little confused about how to use this code – where should we put this to be able to successfully call it?
Thanks!
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].
Nice one! I had a go at imageWithContentsOfFile before checking this post, so finding this saved me time :)
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.
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
@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 …
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:
Try:
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.
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
Glad to hear it – I’ve updated the code.
Pingback: Universal launch images with PhoneGap
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:
Whoops! there’s a typo in my if-condition. it should be:
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.
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!
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:
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
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 =)
Pingback: How to name your png files for iPhone 4 app? « yimingcheung