Question

My app has a large size because it's universal and designed for Retina displays. I want to allow the user to download Retina images from my server instead of including them in the app initially.

I tried this with the code below. The only problem is that the images are being stored in the Documents folder, and the app won't recognise them as Retina images

UIImage *image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://www.test.com/img2@2x.png"]]];
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *pngFilePath = [NSString stringWithFormat:@"%@/img2@2x.png",docDir];
NSData *data1 = [NSData dataWithData:UIImagePNGRepresentation(image)];
[data1 writeToFile:pngFilePath atomically:YES];

How should I be saving the images to allow the app to use them?

Was it helpful?

Solution

The imageWithData: method will always create an image with a scale of 1.0 (non-retina). Creating a retina-aware UIImage from a custom location outside the bundle, you need to use the initWithCGImage:scale:orientation: method:

UIImage *image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://www.test.com/img2@2x.png"]]];
UIImage *retinaImage = [UIImage initWithCGImage:[image CGImage] scale:2.0 orientation:UIImageOrientationUp];

(and, obviously, you shouldn't download the image synchronously...).

I needed to do the same thing (loading scale-dependent images from the documents directory) in my last project, so I've written a small convenience method in a UIImage category:

- (id)initWithContentsOfResolutionIndependentFile:(NSString *)path
{
    if([[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 initWithContentsOfFile:path];
}

Usage:

UIImage *myImage = [[UIImage alloc] initWithContentsOfResolutionIndependentFile:@"/path/to/image.png"];

This will try to load the image from /path/to/image@2x.png when on retina, and use /path/to/image.png otherwise.

OTHER TIPS

It's not working because the source of UI images is the Bundle not NSDocumentDirectory folder. To use retina and non-retina images, you should detect if device is retina and load images programmatically from NSDocumentDirectory.

You can use this to retina detection.

You can use [UIImage imageWithData: scale:] and be able to skip the "@2x" naming convention:

- (UIImage *)getRetinaSafeImage: (NSString *)fileName
{
    // alternatively you can use NSURL instead of path
    NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    NSData *myImageData = [NSData dataWithContentsOfFile: [NSString stringWithFormat:@"%@/%@",docDir, fileName]];

    if([[UIScreen mainScreen] scale] == 2.0)
        return [UIImage imageWithData:myImageData scale:2.0];
    else return [UIImage imageWithData:myImageData];
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top