如何在prepareForInterfaceBuilder中加载图片,使用IBDesignable UIImageView。

26

我想在IB designable UIImageView中加载一张样例图片,在编辑界面时在Interface Builder中展示。以下代码无效,因为IB中的视图占位符仍然为空(视图区域仅包含UIImageView文本):

@IBDesignable
class TestImageView : UIImageView
{
    override func prepareForInterfaceBuilder() {
        //let bundle = NSBundle.mainBundle()
        let bundle = NSBundle(forClass: nil)
        let imagePath = bundle.pathForResource("Test", ofType: "jpg")
        self.image = UIImage(contentsOfFile: imagePath)
    }
}
请注意:
  • 在 IB 中,视图的自定义类(Custom Class)是正确的(TestImageView)。
  • Test.jpg 存在于项目中(如果我在 IB 中手动设置 UIImageView 的图像属性,则图像会显示出来)。
  • 我尝试了两种不同的获取代码中存在的 bundle 的方法。

这是使用 Xcode 6 beta 3 进行测试的。

更新: 在两种情况下,我得到的 bundle 路径都是 "/Applications/Temporary/Xcode6-Beta3.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Overlays"。在该路径下,显然不存在该图像。


发布了一个雷达到苹果rdar://17762946。 - mxb
1
你应该在 prepareForInterfaceBuilder 中调用 super,不是吗? - Fattie
7个回答

53
尝试像这样获取类的包:
let bundle = NSBundle(forClass: self.dynamicType)

或者像这样指定类名

let bundle = NSBundle(forClass: TestImageView.self)

假设你的图片在资源束(bundle)中,例如Images.xcassets,那么你可以使用以下方法加载它:

self.image = UIImage("Test", inBundle: bundle, compatibleWithTraitCollection: self.traitCollection)

请在使用图像之前检查它是否为空。我无法正确地使用bundle.pathForResource获取普通图像资源的图像路径。也没有UIImage调用可以仅指定名称和bundle,因此必须使用特性集合。
这个问题与以下内容相关: xcode 6 IB_DESIGNABLE- not loading resources from bundle in Interface builder 苹果的回应...
工程师已经确定,根据以下原因,此问题的行为符合预期: 我们无法更简单地指定捆绑包。您可能会说,“哦,让我们调用-[NSBundle mainBundle]”,但是很多调用站点引用了一个bundle,不会经过这里(或通过CF API)。然后有人可能会说“好吧,那么我们至少可以交换-[UIImage imageNamed:]”。问题在于,没有一个替代主要bundle。您可能同时加载多个实时视图捆绑包(框架或应用程序),因此我们无法选择其中一个作为主要捆绑包。 开发人员需要了解捆绑包以及如何从捆绑包中获取图像。开发人员应该使用UIImage(named:inBundle:compatibleWithTraitCollection:)来查找所有图像。

谢谢回答,但正确的做法应该是我自己回答中描述的那种。 - mxb
3
我提供了一个捆绑示例,因为我向苹果提交了一个关于在实时视图中找不到资源的错误报告(17656550)。我在我的回答中加入了苹果的回应。 - kasplat
很好的调查,显然当视图作为实时视图呈现时,您必须非常注意其所在的“范围”。 - Lucas van Dongen
1
这在Xcode 9的发行说明中有注明(https://developer.apple.com/library/content/releasenotes/DeveloperTools/RN-Xcode/Chapters/Introduction.html#//apple_ref/doc/uid/TP40001051-CH1-SW878),作为已知问题:“在prepareForInterfaceBuilder()中使用Swift图像字面量导致IBDesignablesAgentCocoaTouch崩溃,因为实时视图编译在不同的包中。(28676479) 解决方法:对于所有图像查询使用UIImage(named:inBundle:compatibleWithTraitCollection:),并使用Bundle(for: type(of: self))查找实时视图类的包。” - cbowns

30

更新至Swift 4.2

当你实例化一个UIImage(使用UIImage(named: "SomeName"))时,应用程序将在你的主束(main bundle)中查找该资源,这通常很有效。但当你处于设计时时,InterfaceBuilder会将可设计视图的代码(用于编译设计)保存在单独的束(bundle)中。

因此解决方案是:动态定义您的束(bundle),因此您的文件可以在设计、编译和运行时间中被找到:

    // DYNAMIC BUNDLE DEFINITION FOR DESIGNABLE CLASS
    let dynamicBundle = Bundle(for: type(of: self))

    // OR ALTERNATIVELY BY PROVDING THE CONCRETE NAME OF YOUR DESIGNABLE VIEW CLASS
    let dynamicBundle = Bundle(for: YourDesignableView.self)

    // AND THEN SUCCESSFULLY YOU CAN LOAD THE RESSOURCE
    let image = UIImage(named: "Logo", in: dynamicBundle, compatibleWith: nil)

你做得很棒。但是为什么需要这个呢? - ScottyBlades
@ScottyBlades:当你运行应用程序时,所有要编译的代码文件通常都在主捆绑包中。但是,在设计时间,InterfaceBuilder将可设计视图的代码(用于在设计时进行编译)保存在单独的捆绑包中。 - LukeSideWalker
在Interface Builder中如何实现这个功能?我尝试在UIImageView中使用"Bundle_name/image_name",但它没有起作用。 - Andrei F

7
让我们来看一下Swift 3的答案。
let bundle = Bundle(for: self.classForCoder)
... UIImage(named: "AnImageInYourAssetsFolderPerhaps", in: bundle, compatibleWith: self.traitCollection)!

6

对于Swift 4(和3),请使用以下内容:

let image = UIImage(named: "foo", in: Bundle(for: type(of: self)), compatibleWith: traitCollection)

这种方法可以普遍获取捆绑包


5

我能够修复问题,从当前的NSProcessInfo对象获取Interface Builder项目路径。然后您可以从IB_PROJECT_SOURCE_DIRECTORIES键中收集正确的路径。

override func prepareForInterfaceBuilder() {
    let processInfo = NSProcessInfo.processInfo()
    let environment = processInfo.environment
    let projectSourceDirectories : AnyObject = environment["IB_PROJECT_SOURCE_DIRECTORIES"]!
    let directories = projectSourceDirectories.componentsSeparatedByString(":")

    if directories.count != 0 {
        let firstPath = directories[0] as String
        let imagePath = firstPath.stringByAppendingPathComponent("PrepareForIBTest/Test.jpg")

        let image = UIImage(contentsOfFile: imagePath)
        self.image = image
    }
}

这项技术在WWDC 2014的411演讲“Interface Builder中的新功能”中有介绍,正如bjhomer在这篇苹果开发者论坛帖子中所建议的那样。
此外,需要注意的是,必须切换到UIView子类,因为似乎UIImageView的外观在实时视图中不会改变。

1
在我看来,正确的回应要么是在这篇帖子中使用 #kasplat,要么是在这个链接中@rickster:https://dev59.com/questions/CGAf5IYBdhLWcg3weyse - SwiftArchitect
我接受了这个答案,因为它基于WWDC会议,所以对我来说就像是来自苹果的官方信息。你觉得呢? - mxb
看起来苹果现在提供了一个更简单的方法:prepareForInterfaceBuilder只能带你走到这里,需要非常具体的操作。另一方面,NSBundle:forClass则是灵活和封装良好的。也许苹果现在正在针对 bug(17656550)提供更好、更轻量级的解决方案? - SwiftArchitect

0

对于Swift 2和Xcode 7,界面已经发生了变化。应该使用

let bundle = NSBundle(forClass: self.dynamicType)
let image = UIImage(named: "imageName", inBundle: bundle, compatibleWithTraitCollection: self.traitCollection)
let imageView = UIImageView(image: image)

我在我的项目中使用它,无论是在IB还是设备上都可以正常工作。


谢谢。但我不明白它与@kasplat的答案有什么不同。 - mxb
UIImage的初始化方式不同。 - Jake Lin

0
这将加载您的IB_Designable,或者如果没有,则加载默认图像。
- (void)prepareForInterfaceBuilder
{
    NSBundle *bundle = [NSBundle bundleForClass:[self class]];
    imagePic = imagePic ? [imagePic imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] : [UIImage imageNamed:@"goodIcon" inBundle:bundle compatibleWithTraitCollection:self.traitCollection];

}

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