12

As many people are complaining it seems that in the Apple SDK for the Retina Display there's a bug and imageWithContentsOfFile actually does not automatically load the 2x images.

I've stumbled into a nice post how to make a function which detects UIScreen scale factor and properly loads low or high res images ( http://atastypixel.com/blog/uiimage-resolution-independence-and-the-iphone-4s-retina-display/ ), but the solution loads a 2x image and still has the scale factor of the image set to 1.0 and this results to a 2x images scaled 2 times (so, 4 times bigger than what it has to look like)

imageNamed seems to accurately load low and high res images, but is no option for me.

Does anybody have a solution for loading low/high res images not using the automatic loading of imageNamed or imageWithContentsOfFile ? (Or eventually solution how to make imageWithContentsOfFile work correct)

Zoe
  • 27,060
  • 21
  • 118
  • 148
Marin Todorov
  • 6,377
  • 9
  • 45
  • 73

6 Answers6

13

Ok, actual solution found by Michael here : http://atastypixel.com/blog/uiimage-resolution-independence-and-the-iphone-4s-retina-display/

He figured out that UIImage has the method "initWithCGImage" which also takes a scale factor as input (I guess the only method where you can set yourself the scale factor)

[UIImage initWithCGImage:scale:orientation:]

And this seems to work great, you can custom load your high res images and just set that the scale factor is 2.0

The problem with imageWithContentsOfFile is that since it currently does not work properly, we can't trust it even when it's fixed (because some users will still have an older iOS on their devices)

Marin Todorov
  • 6,377
  • 9
  • 45
  • 73
11

We just ran into this here at work. Here is my work-around that seems to hold water:

NSString *imgFile = ...path to your file;
NSData *imgData = [[NSData alloc] initWithContentsOfFile:imgFile];
UIImage *img = [[UIImage alloc] initWithData:imgData];
Lisa Rosselli
  • 111
  • 1
  • 3
9

imageWithContentsOfFile works properly (considering @2x images with correct scale) starting iOS 4.1 and onwards.

bioffe
  • 6,283
  • 3
  • 50
  • 65
  • 1
    Despite what documentation says, `imageWithContentsOfFile:` doesn't work on absolute paths. Tested on iOS 5.1 – bentford Apr 12 '12 at 18:54
  • 1
    It doesn't work on absolute path of images in the Caches folder for me. Will investigate further, maybe it was a bad test. – bentford Apr 12 '12 at 22:17
  • Does not work on iOS 5.1 sim for retina iPad. Using [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"image.png" ofType:nil]] – Jarson Apr 23 '12 at 18:36
  • 1
    @Jarson I use it almost the same way, but differently [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"image" ofType:@"png"]] – bioffe Apr 23 '12 at 19:22
  • @bioffe I tried separating the extension into the type specifier and there was no change in behavior. – Jarson Apr 24 '12 at 17:25
  • @Jarson my suggestion for you is to post your code. It works for me ever since iOS 4.1 for me. – bioffe Apr 24 '12 at 18:25
  • 4
    +1 I can confirm that @2x loading *does* work for me using imageWithContentsOfFile on iOS5.1 and iOS6 using retina simulators (both iPhone and iPad). If anyone is checking in the simulator, ensure you're running the Retina version of the simulator! – occulus Nov 27 '12 at 10:35
  • I agree with @occulus, it's working. Don't check the size of the image, because this is in points, not in pixels. – xarly Apr 15 '14 at 10:20
6

Enhancing Lisa Rossellis's answer to keep retina images at desired size (not scaling them up):

NSString *imagePath = ...Path to your image
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfFile:imagePath] scale:[UIScreen mainScreen].scale];
Gasper
  • 5,679
  • 1
  • 18
  • 21
5

I've developed a drop-in workaround for this problem. It uses method swizzling to replace the behavior of the "imageWithContentsOfFile:" method of UIImage. It works fine on iPhones/iPods pre/post retina. Not sure about the iPad.

Hope this is of help.

#import </usr/include/objc/objc-class.h>

@implementation NSString(LoadHighDef)

/** If self is the path to an image, returns the nominal path to the high-res variant of that image */
-(NSString*) stringByInsertingHighResPathModifier {

     NSString *path = [self stringByDeletingPathExtension];

     // We determine whether a device modifier is present, and in case it is, where is 
     // the "split position" at which the "@2x" token is to be added
     NSArray  *deviceModifiers = [NSArray arrayWithObjects:@"~iphone", @"~ipad", nil];
     NSInteger splitIdx = [path length];
     for (NSString *modifier in deviceModifiers) {
          if ([path hasSuffix:modifier]) {
               splitIdx -= [modifier length];
               break;
          }
     }

     // We insert the "@2x" token in the string at the proper position; if no 
     // device modifier is present the token is added at the end of the string
     NSString *highDefPath = [NSString stringWithFormat:@"%@@2x%@",[path substringToIndex:splitIdx], [path substringFromIndex:splitIdx]];

     // We possibly add the extension, if there is any extension at all
     NSString *ext = [self pathExtension];
     return [ext length]>0? [highDefPath stringByAppendingPathExtension:ext] : highDefPath;
}

@end

@implementation UIImage (LoadHighDef)

/* Upon loading this category, the implementation of "imageWithContentsOfFile:" is exchanged with the implementation
 * of our custom "imageWithContentsOfFile_custom:" method, whereby we replace and fix the behavior of the system selector. */
+(void)load {
     Method originalMethod    = class_getClassMethod([UIImage class], @selector(imageWithContentsOfFile:));
     Method replacementMethod = class_getClassMethod([UIImage class], @selector(imageWithContentsOfFile_custom:));
     method_exchangeImplementations(replacementMethod, originalMethod);
}

/** This method works just like the system "imageWithContentsOfFile:", but it loads the high-res version of the image 
 *  instead of the default one in case the device's screen is high-res and the high-res variant of the image is present.
 *
 *  We assume that the original "imageWithContentsOfFile:" implementation properly sets the "scale" factor upon 
 *  loading a "@2x" image . (this is its behavior as of OS 4.0.1).
 *
 *  Note: The "imageWithContentsOfFile_custom:" invocations in this code are not recursive calls by virtue of 
 *  method swizzling. In fact, the original UIImage implementation of "imageWithContentsOfFile:" gets called.
 */

+ (UIImage*) imageWithContentsOfFile_custom:(NSString*)imgName {

     // If high-res is supported by the device...
     UIScreen *screen = [UIScreen mainScreen];
     if ([screen respondsToSelector:@selector(scale)] && [screen scale]>=2.0) {

          // then we look for the high-res version of the image first
          UIImage  *hiDefImg = [UIImage imageWithContentsOfFile_custom:[imgName stringByInsertingHighResPathModifier]];

          // If such high-res version exists, we return it
          // The scale factor will be correctly set because once you give imageWithContentsOfFile:
          // the full hi-res path it properly takes it into account 
          if (hiDefImg!=nil)
               return hiDefImg;
     }

     // If the device does not support high-res of it does but there is
     // no high-res variant of imgName, we return the base version
     return [UIImage imageWithContentsOfFile_custom:imgName];
}

@end
Marco B
  • 71
  • 2
  • Hi Marco, as described in the question - the names of the files are not a problem at all, but the scale factor of the image itself. – Marin Todorov Sep 23 '10 at 15:13
  • Hi Ican, my code properly sets the scale factor (tested in simulator with OS 4.0.1 and 4.1). The point is that while imageWithContentsOfFile: does not automatically find the "@2x" variant, as you say, once you instruct it to get the "@2x" file directly (as in my swizzled method) it then properly sets the scale factor. Just drop my two categories somewhere in your code where they get compiled and try using imageWithContentsOfFile: as you would if it were not broken – Marco B Sep 23 '10 at 16:44
3

[UIImage imageWithContentsOfFile:] doesn't load @2x graphics if you specify an absolute path.

Here is a solution:

- (UIImage *)loadRetinaImageIfAvailable:(NSString *)path {

    NSString *retinaPath = [[path stringByDeletingLastPathComponent] stringByAppendingPathComponent:[NSString stringWithFormat:@"%@@2x.%@", [[path lastPathComponent] stringByDeletingPathExtension], [path pathExtension]]];

    if( [UIScreen mainScreen].scale == 2.0 && [[NSFileManager defaultManager] fileExistsAtPath:retinaPath] == YES) 
        return [[[UIImage alloc] initWithCGImage:[[UIImage imageWithData:[NSData dataWithContentsOfFile:retinaPath]] CGImage] scale:2.0 orientation:UIImageOrientationUp] autorelease];
    else
        return [UIImage imageWithContentsOfFile:path];
}

Credit goes to Christof Dorner for his simple solution (which I modified and pasted here).

bentford
  • 33,038
  • 7
  • 61
  • 57