UIImage imageNamed需要使用pathForResource吗?

16

在使用imageNamed创建UIImage时,使用NSBundle方法pathForResource搜索图像路径有多重要?我看到一些教程代码只直接指定图像名称,还有一些则需要先查找路径。

从我的经验来看,我总是直接使用名称,这样做通常都能正常工作。我认为它会自动知道如何找到图像。在什么情况下需要更进一步查找路径呢?这个问题的重要性是什么?


这里有一个关于在代码中使用imageNamed的SO帖子,你可能也想阅读一下。虽然与你的问题没有直接关系,但我发现它非常有用。 - zero0cool
4个回答

66

根本不需要...是回答原问题的答案:

  

在使用imageNamed创建UIImage时,使用NSBundle方法pathForResource搜索图像路径有多重要?

并不重要...这是Zoul和Ranga所接受的答案以及其他答案的正确性。公平地说,如果你谈论应用程序包目录结构,或者图像在Xcode“蓝色”文件夹中(稍后会提到更多),则它们是正确的,但对于大多数情况而言并不是。

无论如何,让我们来看看真正的答案。

像往常一样,我在寻找自己的答案时发现了这个问题。 我从未找到文档或其他回答令人满意的答案,因此我决定进行测试。

我的测试细节都在下面,但是让我在这里总结一下结果。

简而言之,当使用imageNamed:加载图像时,取决于您放置它们的位置:

  1. 如果您的图像位于项目的根目录中,即使以纯逻辑Xcode组织,那么不需要考虑路径:只需图像名称。

  2. 如果您的图像位于附加到文件系统目录的组中,通过“为添加的文件夹创建组”,则仍然无需担心名称。

  3. 如果您的图像位于“蓝色”组中,该组通过“为添加的文件夹创建文件夹引用”附加到文件系统目录,则可以像上面接受的答案建议的那样指定相对路径来使用imageNamed:加载它。

  4. 如果您使用imageWithContentsOfFile:作为imageNamed:的主要替代方法,则确实需要完整的文件路径,包括捆绑路径,这意味着您需要知道Xcode导航器结构如何转换为捆绑目录结构中的路径。

这两种方法之间的其他重要区别:

  • imageNamed方法不需要指定文件类型的扩展名,因此可以仅使用 "icon" 而不是 "icon.png",而imageWithContentsOfFile方法需要完整的文件名
  • 这个优点可以帮助第二个特性:如果有retina版本的图像,imageNamed会自动加载它,只需在文件名后添加@2x即可。因此,如果你请求 "icon",在retina显示器上它将尝试加载 "icon@2x.png"。但是imageWithContentsOfFile则不行。
  • imageNamed方法会缓存图像,因此引起争议:如果你在SO或者其他网站搜索相关帖子,你会发现很多人建议避免使用它,因为它无法正确清除其缓存。然而,这个问题几年前已经被修复,所以你不必担心它无法清除缓存。但是你仍然需要关注它是否缓存了图像。如果你的图像很大且不经常加载,则通过从文件中加载它们而不是缓存它们来节省内存。这与泄漏无关:即使没有泄漏,设备上的内存仍然是有限的,你不希望无谓地缓存它们。这是经典的缓存权衡:在你的情况下,哪个更重要?内存性能还是CPU性能(时间)。

接下来是我的测试过程。

我创建了一个简单的UITableView应用程序,并使用不同的方法在表的行中显示3个简单图标文件。这些图标在Xcode项目结构中的位置不同。需要注意的是,重点在于Xcode。理解对原始问题的回答的关键是,在iOS应用程序中有三种完全不同的项目目录结构:你在Xcode导航器中看到的目录结构,你在Finder中看到的相同项目的文件系统上的目录结构(右键单击Xcode导航器中的任何项,然后选择“在Finder中显示”),以及你很少看到的部署应用程序的 "bundle" 目录结构。你也可以在Finder中找到这最后一个 - 通过在 ~/Library/Application Support/iPhone模拟器中找到你的应用程序,并深入到.app目录中。我待会儿会给你展示一张图片。

因此,在我的应用程序中,我以不同的方式将所有三个图标png图像文件拖入Xcode中:

  1. icon1.png(一个时钟),我将其作为文件拖到Xcode项目的根目录中,后来我创建了一个新组并将其拖入其中。这个组在文件系统中没有任何目录表示:它是一个纯粹的Xcode组。因此它的名称是:“JustGroup”。

  2. icon2.png(一只眼睛),我最初将我的文件系统放在名为“RealDir”的目录中,并将整个目录拖入Xcode,当询问时,我选择了“为添加的任何文件夹创建组”选项。这意味着Xcode中的RealDir组附加到文件系统中名为RealDir的实际目录(在我的项目目录中),而icon2.png就在其中。

  3. icon3.png(一个目标),我也放在了一个单独的目录中,然后将其拖入Xcode。只是这一次,我选择了第二个单选按钮“为添加的任何文件夹创建文件夹引用”。这会在Xcode中创建一个所谓的“蓝色”组。稍后会详细介绍这是什么意思。我将该组(以及目录)命名为“FolderReference”。

这里是Xcode给你的选项截图: Xcode's dialog when dragging a directory in

这是我在Xcode中的项目结构: Xcode navigator project structure

现在,在我的应用程序中,我使用了两种方法来加载每个图标:UIImage imageNamed:和UIImage imageWithContentsOfFile。我创建了一堆行,每个单元格的标题都是包含图标的组的名称:JustGroup、RealDir或FolderReference,以及所使用的方法的名称:imageNamed与fromFile(我使用它作为imageWithContentsOfFile的缩写)

单元格的详细标签(标题下面的淡色文本)显示了我给该方法指定的文件或路径名。

要清楚,在“fromFile”的情况下,我将“相对”名称与包路径相加。因此,对于“fromFile”,我实际上使用的是以下代码:

NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
NSString *imagePath = [NSString stringWithFormat:@"%@/%@", bundlePath, filePath];
UIImage *image = [UIImage imageWithContentsOfFile:imagePath];

“filePath”是您在表单单元格详细标签中看到的路径。另一方面,在imageNamed:中,单元格详细信息中的filePath会原样传递。

而行上的image自然是加载的图像。因此,在表格中没有图像的行中,图像加载失败

简而言之,这里是结果。如果您没有阅读此帖子,请至少浏览一下此图片,它将告诉您所需的所有内容。

app shows which icons were loaded

以下是易于理解的基本说明:

  • 正如官方文档中所述,imageNamed:方法从应用程序捆绑包中加载图像。这意味着您不需要指定捆绑包位置,只需要指定文件名。甚至仅需要文件的基本名称。文档在这里有点薄弱,应该明确表示它从给定文件路径相对于应用程序捆绑包根目录加载图像。

  • (这是关键,请注意)关于捆绑目录的规则,指的是您部署的应用程序捆绑包中的根目录。如果您进行探索,这意味着位于“.app”目录本身中。那不是Xcode导航器中Xcode项目的根目录,也不是Finder中Xcode项目的根目录。

  • 这是因为,在将应用程序部署到设备(或模拟器)时,所有由“添加文件夹的组”表示的项目目录都会被扁平化。也就是说,该目录被忽略了,并且所有内容都被毫不客气地倾倒到捆绑包的根目录中。(我说“毫不客气”,因为如果不同文件夹中存在具有相同名称的文件,则它们将在此处碰撞,并且您将得不到任何帮助来解决导致问题的原因。)这是我示例中RealDir的情况:在部署的应用程序中,RealDir不再存在,而icon2.png留下来与其他人混在一起(可怕)。几乎可以毫不费力地说,“JustGroup”,纯逻辑的Xcode组,也被忽略了——它从来不是真正的目录,只是Xcode用户的视觉辅助——而icon1.png也在捆绑包根目录中。

    • 这就是为什么imageNamed:能够加载icon2的原因。

    • 也是为什么imageWithContentsOfFile无法在“RealDir/image2.png”中找到它的原因:因为部署的应用程序中没有RealDir目录

"蓝色文件夹",也就是用"添加文件夹的引用"表示的目录,实际上被保留在应用程序捆绑目录结构中。这似乎是蓝色文件夹的重点:它们为您提供了一种在部署的应用程序中创建目录结构的方法。我不确定最初的理由是什么,但一个很好的用例是当您有几个包含同名资源文件的替代版本的目录,并且您希望您的应用程序能够通过更改目录在运行时在它们之间进行切换。无论如何,在我的FolderReference中的icon3.png文件在部署的应用程序中仍然保留在我的FolderReference目录中。

  • 这就是为什么imageNamed:不能使用"icon3"找到它,但可以使用"FolderReference/icon3"
  • imageWithContentsOfFile也能够使用FolderReference找到它,但只有在使用上面的代码将其附加到完整的包路径时才能找到。(这里的关键区别是:imageNamed在这种情况下使用相对路径工作,imageWithContentsOfFile始终使用绝对路径工作)。

为了澄清,这是我的文件夹结构:

你看到了我的Xcode项目导航器结构,这是它下面的文件系统目录: Finder Xcode project directory structure

最后,也许最重要的是,部署的捆绑文件系统目录结构: deployed bundle directory structure

注意:我在我的Mac上找到了这个位置:您可以在类似的位置找到您的位置-您可能需要搜索一下,找到哪个丑陋的GUID命名的子目录包含您的应用程序。

 ~/Library/Application Support/iPhone Simulator/5.1/Applications/4EB386B2-CD7E-4590-9757-18DDDEE6AF4F/ImageLoadingTest.app 

希望这能帮到你。测试、探索,最后描述它确实对我有所帮助。


这本应该是答案,无论如何感谢您的发布。 - dashesy
这是一个非常有帮助的答案,为我解决了这个问题。本应该是被采纳的答案。已点赞并感谢。 - scottburton11
请问您在哪里找到了“部署的捆绑文件系统目录结构”?我在'/Users/cdesign/Library/Application Support/iPhone Simulator/6.1/Applications/B458A3F5-5B21-49CD-B4D8-17E5189678FA'中找不到它,该目录只包含“文档”,“库”,“tmp”和“myapp.app”。 “文档”只包含对应于NSDocumentDirectory的图像... - Charles Robertson

5
文档中表示“该方法查找应用程序主包中指定名称的图像”,因此我认为您只需使用名称即可。唯一的例外可能是存储在子文件夹内的图像,特别是当您有 foo / image.png bar / image.png 时。我不知道 [UIImage imageNamed:@“foo / image”] 是否有效,但这很容易尝试。
(在这些情况下有点令人困惑的是,Xcode树中的组与生成的应用程序捆绑包中的文件夹不对应。它们的内容被压缩到捆绑包的根目录中,除非您使用蓝色文件夹引用而不是常规组。)

2
实际上,你是正确的,它确实有效,但(这是一个很大的但)仅当“foo”是你的捆绑包中的目录,也就是在 Xcode 中的“蓝色”文件夹。在最常见的情况下,您根本不需要“foo”。(警告:如果您只有文档和猜测,您不应该回答问题;如果答案无法解决您的问题,您也不应该接受答案;如果您有一个蓝色的文件夹,那么一切都好,如果没有,那么这对您来说不会起作用。)请参见下面关于此主题的冗长论文。 - Rhubarb

0
我创建了一个新的Xcode项目(单视图,AppDelelgate,ViewController类,故事板等)。 创建了一个图片组。 使用Paintbrush创建了一个16x16 png文件Wall1.png,并将其拖放到Xcode中的Images组中(让Xcode复制文件)。
在ViewController的viewDidLoad方法中添加了以下代码:
UIImageView* imageView=[[UIImageView alloc] initWithFrame:CGRectMake(50, 100, 16, 16)];
UIImage *image = [UIImage imageWithContentsOfFile: [[NSBundle mainBundle] pathForResource:@"Images/Wall1" ofType:@"png"]];
imageView.image = image;
    UIImageView* imageView=[[UIImageView alloc] initWithFrame:CGRectMake(50, 100, 16, 16)];
UIImage *image = [UIImage imageWithContentsOfFile: [[NSBundle mainBundle] pathForResource:@"Wall1" ofType:@"png"]];
imageView.image = image;
[self.view addSubview:imageView];

在我的手机上运行应用程序,图像无法显示

在[self.view addSubview:imageView];处添加断点;

图像为空

打开终端并更改目录到我的项目,Wall1.png不是组文件夹Images。从项目中删除png,创建一个Images文件夹,将Wall1.png移动到该文件夹中。将现有文件Wall1.png添加到组Images中。

运行应用程序,图像仍然无法显示。

图像为空

将Images/Wall1更改为Wall1

运行应用程序,瞬间图像就显示出来了

如果您为图像创建了一个组,则Xcode不会创建相应的目录。如果您想要(我更喜欢将我的图像放在单独的文件夹中),请手动创建一个。在使用UIImage imageWithContentsOfFile时,请勿指定图像文件的完整路径。


-1

试试这个。

[UIImage imageNamed:@"your directory path of image"]

[UIImage imageNamed:@"Dir1/folder1/folder2/imagename.jpeg"]


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