xcode 6 IB_DESIGNABLE- not loading resources from

2019-01-10 19:02发布

I am trying to make a custom control that updates live in Interface Builder using the new IB_DESIGNABLE option described here.

- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();

CGRect myFrame = self.bounds;
CGContextSetLineWidth(context, 10);
CGRectInset(myFrame, 5,5);
[[UIColor redColor] set];
UIRectFrame(myFrame);

NSBundle *bundle = [NSBundle mainBundle];
NSString *plistPath;

plistPath = [bundle pathForResource:@"fileExample" ofType:@"plist"];
UIImage *tsliderOff = [UIImage imageNamed:@"btn_slider_off.png"];

[tsliderOff drawInRect:self.bounds];
}

When I run in the simulator, I get a red box with my image in the center (as expected): enter image description here

But when I try use the Interface Builder, it only shows up as a red box (no image in the middle): Rendered in Interface Builder

When I debug it: Editor->Debug Selected Views, it reveals that anything loaded from the bundle is nil. The plistPath and tsliderOff both appear as nil.

I made sure that btn_slider_off.png was included in the Targets->myFrameWork->Build Phases->Copy Bundle Resources.

Any idea why Interface Builder doesn't see the png file during editing, but shows ok when running? "Creating a Custom View that Renders in Interface Builder" is a little limited if I can't load any images to render...


edit based on solution by rickster

rickster pointed me to the solution - the problem is that the files don't live in mainBundle, they live in [NSBundle bundleForClass:[self class]]; And it appears that [UIImage imageNamed:@"btn_slider_off.png"] automatically uses mainBundle.

The following code works!

#if !TARGET_INTERFACE_BUILDER
NSBundle *bundle = [NSBundle mainBundle];
#else
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
#endif
NSString *fileName = [bundle pathForResource:@"btn_slider_off" ofType:@"png"];
UIImage *image = [UIImage imageWithContentsOfFile:fileName];
[image drawInRect:self.bounds];

6条回答
【Aperson】
2楼-- · 2019-01-10 19:33

I've fixed it using the following line of code:

let image = UIImage(named: "image_name", inBundle: NSBundle(forClass: self.dynamicType), compatibleWithTraitCollection: nil)

After implementing it this way the images show up correctly in the Interface Builder

查看更多
smile是对你的礼貌
3楼-- · 2019-01-10 19:34

Warning about the above bundle/path resolution while in IB_DESIGNABLE:

XCode will not resolve a resource if the call contained in the initialization of the UIView. I could only get XCode to resolve a path while in drawRect:

I found a great / easy workaround to the above bundle resolution techniques, just simply designate a IBInspectable property to be a UIImage, then specify the image in Interface Builder. Ex:

@IBInspectable var mainImage: UIImage?
@IBInspectable var sideImage: UIImage?
查看更多
姐就是有狂的资本
4楼-- · 2019-01-10 19:39

As of when this question was first asked, creating an IB-designable control required packaging it in a framework target. You don't have to do that anymore — the shipping Xcode 6.0 (and later) will preview IB-designable controls from your app target, too. However, the problem and the solution are the same.

Why? [NSBundle mainBundle] returns the primary bundle of the currently running app. When you call that from a framework, you're getting a different bundle returned based on which app is loading your framework. When you run your app, your app loads the framework. When you use the control in IB, a special Xcode helper app loads the framework. Even if your IB-designable control is in your app target, Xcode is creating a special helper app to run the control inside of IB.

The solution? Call +[NSBundle bundleForClass:] instead (or NSBundle(forClass:) in Swift). This gets you the bundle containing the executable code for whichever class you specify. (You can use [self class]/self.dynamicType there, but beware the result will change for subclasses defined in different bundles.)

If you're using the framework approach — which can be useful for some apps even though it's no longer required for IB-designable controls — it's best to put image resources in the same framework with the code that uses them. If your framework code expects to use resources provided at run time by whatever app loads the framework, the best thing to do for making it IB-designable is to fake it. Implement the prepareForInterfaceBuilder method in your control and have it load resources from a known place (like the framework bundle or a static path in your Xcode workspace).

查看更多
姐就是有狂的资本
5楼-- · 2019-01-10 19:42

IN SWIFT 3.0 the code of @rickster has changed to:

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

// OR ALTERNATEVLY BY PROVDING THE CONCRETE NAME OF DESIGNABLE CLASS
let theBundle : Bundle = Bundle(for: RH_DesignableView.self)

// AND THEN SUCCESSFULLY YOU CAN LOAD THE RESSOURCE
let theImage  : UIImage? = UIImage(named: "Logo", in: theBundle, compatibleWith: nil)
查看更多
做自己的国王
6楼-- · 2019-01-10 19:54

I also had the same problem : looking at this answer and following WWDC 2014 whats new in Interface Builder. I solved it like that:

- (void)prepareForInterfaceBuilder{
     NSArray *array =    [[NSProcessInfo processInfo].environment[@"IB_PROJECT_SOURCE_DIRECTORIES"] componentsSeparatedByString:@":"];
     if (array.count > 0) {
         NSString *str = array[0];
         NSString *newStr =  [str stringByAppendingPathComponent:@"/MyImage.jpg"];
         image = [UIImage imageWithContentsOfFile:newStr]; 
     }
}
查看更多
Evening l夕情丶
7楼-- · 2019-01-10 19:58

I ran into a similar problem in Swift. As of Xcode 6 beta 3, you don't need to use a framework to get live rendering. However, you still have to deal with the bundle problem for Live View so that Xcode knows where to find the assets. Assuming "btn_slider_off" is an image set in Images.xcassets then you can do this in Swift for live rendering and it will work when the app is run normally as well.

let name = "btn_slider_off"
let myBundle = NSBundle(forClass: self.dynamicType)
// if you want to specify the class name you can do that instead
// assuming the class is named CustomView the code would be
// let myBundle = NSBundle(forClass: CustomView.self)
let image = UIImage(named: name, inBundle: myBundle, compatibleWithTraitCollection: self.traitCollection)
if let image = image {
    image.drawInRect(self.bounds)
}
查看更多
登录 后发表回答