I am trying to make a custom control that updates live in Interface Builder using the new IB_DESIGNABLE option described here.
我正在尝试使用这里描述的新的IB_DESIGNABLE选项创建一个自定义控件来实时更新接口构建器。
- (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):
当我在模拟器中运行时,我得到了一个红色的方框,中间有我的图像(如预期):
But when I try use the Interface Builder, it only shows up as a red box (no image in the middle):
但是当我尝试使用界面构建器时,它只显示为一个红色的方框(中间没有图像):
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.
当我调试它:Editor->调试选定的视图时,它显示从bundle加载的任何内容都为nil。plistPath和tsliderOff都显示为nil。
I made sure that btn_slider_off.png was included in the Targets->myFrameWork->Build Phases->Copy Bundle Resources.
我确保了btn_slider_off。png被包括在目标->框架->构建阶段->复制包资源中。
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...
你知道为什么界面构建器在编辑时看不到png文件,但是在运行时显示ok吗?“创建一个自定义视图,并在界面构建器中呈现”如果我不能加载任何图片来呈现的话,就有点限制了……
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.
rickster给我指出了解决方案——问题是这些文件不在mainBundle中,而是在[NSBundle bundleForClass:[self class]]中;看起来[UIImage imageNamed:@"btn_slider_off。使用mainBundle自动png”)。
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 个解决方案
#1
48
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.
当第一次问到这个问题时,创建一个ibm可设计控件需要将它打包到一个框架目标中。您不必再这样做了——发货Xcode 6.0(稍后)也将在您的应用程序目标中预览ibm -designable控件。然而,问题和解决方案是一样的。
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.
为什么?[NSBundle mainBundle]返回当前运行的应用程序的主包。当您从框架调用该包时,您将得到一个不同的包,该包基于哪个应用程序正在加载您的框架。运行应用程序时,应用程序会加载框架。当您在IB中使用控件时,一个特殊的Xcode助手应用程序将加载框架。即使您的ibm可设计控件在您的应用程序目标中,Xcode也正在创建一个特殊的助手应用程序来运行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.)
解决方案?调用+[NSBundle bundleForClass:](或者在Swift中调用NSBundle(forClass:))。这将为您提供包含您指定的任何类的可执行代码的包。(你可以使用[self class]/self。这里有dynamicType,但是要注意,对于不同bundle中定义的子类,结果会发生变化。
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).
如果您正在使用框架方法——这对于某些应用程序是有用的,尽管ibm可设计控件不再需要它——最好将图像资源与使用它们的代码放在同一个框架中。如果您的框架代码希望在运行时使用任何应用程序加载框架所提供的资源,那么最好的方法就是将它设置为可编程的。在控件中实现prepareForInterfaceBuilder方法,并让它从已知位置(比如框架包或Xcode工作区中的静态路径)加载资源。
#2
13
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.
我在斯威夫特遇到了一个类似的问题。从Xcode 6 beta 3开始,您不需要使用框架来获得实时渲染。但是,您仍然需要处理Live View的bundle问题,以便Xcode知道在哪里查找资产。假设“btn_slider_off”是一个图像集。然后你可以在Swift中进行实时渲染,当应用程序正常运行时它也会工作。
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)
}
#3
3
Warning about the above bundle/path resolution while in IB_DESIGNABLE:
在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:
如果在UIView的初始化中包含调用,XCode将不会解析资源。在drawRect中,我只能让XCode解析一个路径:
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:
我在上面的bundle resolution技术中找到了一个很好的/简单的方法,只是简单地将IBInspectable属性指定为一个UIImage,然后在Interface Builder中指定图像。例:
@IBInspectable var mainImage: UIImage?
@IBInspectable var sideImage: UIImage?
#4
3
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
这样实现之后,图像会在接口构建器中正确显示
#5
1
IN SWIFT 3.0 the code of @rickster has changed to:
在SWIFT 3.0中,@rickster代码改为:
// 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
0
I also had the same problem : looking at this answer and following WWDC 2014 whats new in Interface Builder. I solved it like that:
我也有同样的问题:看看这个答案,在WWDC 2014之后界面构建器中有什么新东西。我是这样解的
- (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];
}
}
#1
48
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.
当第一次问到这个问题时,创建一个ibm可设计控件需要将它打包到一个框架目标中。您不必再这样做了——发货Xcode 6.0(稍后)也将在您的应用程序目标中预览ibm -designable控件。然而,问题和解决方案是一样的。
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.
为什么?[NSBundle mainBundle]返回当前运行的应用程序的主包。当您从框架调用该包时,您将得到一个不同的包,该包基于哪个应用程序正在加载您的框架。运行应用程序时,应用程序会加载框架。当您在IB中使用控件时,一个特殊的Xcode助手应用程序将加载框架。即使您的ibm可设计控件在您的应用程序目标中,Xcode也正在创建一个特殊的助手应用程序来运行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.)
解决方案?调用+[NSBundle bundleForClass:](或者在Swift中调用NSBundle(forClass:))。这将为您提供包含您指定的任何类的可执行代码的包。(你可以使用[self class]/self。这里有dynamicType,但是要注意,对于不同bundle中定义的子类,结果会发生变化。
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).
如果您正在使用框架方法——这对于某些应用程序是有用的,尽管ibm可设计控件不再需要它——最好将图像资源与使用它们的代码放在同一个框架中。如果您的框架代码希望在运行时使用任何应用程序加载框架所提供的资源,那么最好的方法就是将它设置为可编程的。在控件中实现prepareForInterfaceBuilder方法,并让它从已知位置(比如框架包或Xcode工作区中的静态路径)加载资源。
#2
13
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.
我在斯威夫特遇到了一个类似的问题。从Xcode 6 beta 3开始,您不需要使用框架来获得实时渲染。但是,您仍然需要处理Live View的bundle问题,以便Xcode知道在哪里查找资产。假设“btn_slider_off”是一个图像集。然后你可以在Swift中进行实时渲染,当应用程序正常运行时它也会工作。
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)
}
#3
3
Warning about the above bundle/path resolution while in IB_DESIGNABLE:
在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:
如果在UIView的初始化中包含调用,XCode将不会解析资源。在drawRect中,我只能让XCode解析一个路径:
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:
我在上面的bundle resolution技术中找到了一个很好的/简单的方法,只是简单地将IBInspectable属性指定为一个UIImage,然后在Interface Builder中指定图像。例:
@IBInspectable var mainImage: UIImage?
@IBInspectable var sideImage: UIImage?
#4
3
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
这样实现之后,图像会在接口构建器中正确显示
#5
1
IN SWIFT 3.0 the code of @rickster has changed to:
在SWIFT 3.0中,@rickster代码改为:
// 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
0
I also had the same problem : looking at this answer and following WWDC 2014 whats new in Interface Builder. I solved it like that:
我也有同样的问题:看看这个答案,在WWDC 2014之后界面构建器中有什么新东西。我是这样解的
- (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];
}
}