如何让[UIImage imageWithContentsOfFile:]和高分辨率图像正常工作

12

很多人在抱怨,似乎在Apple Retina Display的SDK中,imageWithContentsOfFile存在一个bug,实际上并没有自动加载2x图像。

我偶然发现了一篇好文章,介绍了如何编写函数来检测UIScreen的比例因子,并正确加载低分辨率或高分辨率的图片(http://atastypixel.com/blog/uiimage-resolution-independence-and-the-iphone-4s-retina-display/),但是该解决方案加载了2x图像,而图像的比例因子仍设置为1.0,这导致2x图像缩放了2倍(因此,比它看起来要大4倍)

imageNamed似乎可以准确地加载低分辨率和高分辨率的图像,但对我来说不是选项。

是否有人有解决方案,可以加载低/高分辨率的图像而不使用imageNamed或imageWithContentsOfFile的自动加载?(或者有解决方案如何使imageWithContentsOfFile正常工作)

6个回答

13

好的,Michael在这里找到了实际解决方案: http://atastypixel.com/blog/uiimage-resolution-independence-and-the-iphone-4s-retina-display/

他发现UIImage有一个名为"initWithCGImage"的方法,它也可以接受比例因子作为输入(我想这是唯一一个可以自行设置比例因子的方法)。

[UIImage initWithCGImage:scale:orientation:]

这种方法看起来很好,您可以自定义加载高分辨率图像,并将缩放因子设置为2.0

使用imageWithContentsOfFile的问题在于,由于它目前无法正常工作,即使修复了它,我们也不能信任它(因为一些用户仍会在设备上运行旧的iOS)


如果您支持iOS 4.1或更高版本,请参考"bioffe"的答案。这样会简单得多。 - Basil Bourque

11

我们在工作中遇到了这个问题。下面是我的解决方法,看起来很有效:

NSString *imgFile = ...path to your file;
NSData *imgData = [[NSData alloc] initWithContentsOfFile:imgFile];
UIImage *img = [[UIImage alloc] initWithData:imgData];

9

imageWithContentsOfFile 函数在 iOS 4.1 及其以后版本中可以正常工作(考虑到正确比例的 @2x 图像)。


1
尽管文档中如此说明,但imageWithContentsOfFile:在绝对路径上无法正常工作。已在iOS 5.1上进行了测试。 - bentford
1
对我来说,使用缓存文件夹中图像的绝对路径无法正常工作。将进一步调查,可能是一个错误的测试。 - bentford
1
@Jarson 我使用它的方式几乎相同,但是略有不同 [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"image" ofType:@"png"]] - bioffe
@Jarson,我建议你发布你的代码。自从iOS 4.1以来,它一直对我有效。 - bioffe
4
我可以确认,在iOS5.1和iOS6上,使用imageWithContentsOfFile方法加载@2x的图片在Retina模拟器(iPhone和iPad)上是有效的。如果有人在模拟器上进行检查,请确保你正在运行Retina版本的模拟器! - occulus
显示剩余3条评论

6

增强Lisa Rossellis的答案,以保持视网膜图像在所需尺寸(而不是缩放它们):

NSString *imagePath = ...Path to your image
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfFile:imagePath] scale:[UIScreen mainScreen].scale];

5
我已经开发了一个临时解决方案来解决这个问题。它使用方法交换来替换UIImage的“imageWithContentsOfFile:”方法的行为。它在iPhone / iPod的retina之前/之后都可以正常工作。不确定iPad是否适用。希望这能有所帮助。
#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,如问题描述 - 文件的名称完全不是问题,但图像本身的比例因子是个问题。 - Marin Todorov
嗨Ican,我的代码正确设置了比例因子(在模拟器中测试了OS 4.0.1和4.1)。问题是,尽管如你所说的,imageWithContentsOfFile:不能自动找到"@2x"变体,但一旦你指示它直接获取"@2x"文件(如我的方法中替换了该函数),则它会正确地设置比例因子。只需将我的两个类别放在编译它们的代码中的某个位置,并尝试像使用未出错的函数那样使用imageWithContentsOfFile:函数即可。 - Marco B

3

[UIImage imageWithContentsOfFile:] 方法如果你指定了绝对路径,就不能加载@2x分辨率的图像。

以下是解决方案:

- (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];
}

感谢Christof Dorner提供的简单解决方案(我进行了修改并在此处粘贴)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接