objective-c Interface Builder 有没有办法呈现不覆盖 drawRect 的 IBDesignable 视图:
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/26197582/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Is there a way for Interface Builder to render IBDesignable views which don't override drawRect:
提问by Hari Karam Singh
I very rarely override drawRect in my UIView subclasses, usually preferring to set layer.contentswith pre-rendering images and often employing multiple sublayers or subviews and manipulating these based on input parameters. Is there a way for IB to render these more complex view stacks?
我很少在我的 UIView 子类中覆盖 drawRect,通常更喜欢layer.contents使用预渲染图像进行设置,并且经常使用多个子层或子视图并根据输入参数操作它们。IB 有没有办法渲染这些更复杂的视图堆栈?
回答by Hari Karam Singh
Thanks, @zisoft for the clueing me in on prepareForInterfaceBuilder. There a few nuances with Interface Builder's render cycle which were the source of my issues and are worth noting...
谢谢,@zisoft 为我提供了prepareForInterfaceBuilder. Interface Builder 的渲染周期有一些细微差别,这是我问题的根源,值得注意......
- Confirmed: You don't need to use
-drawRect.
- 确认:您不需要使用
-drawRect.
Setting images on UIButton control states works. Arbitrary layer stacks seem to work if a few things are kept in mind...
在 UIButton 控件状态上设置图像有效。如果记住一些事情,任意层堆栈似乎可以工作......
- IB uses
initWithFrame:
- IB用途
initWithFrame:
..not initWithCoder. awakeFromNibis also NOT called.
..不是initWithCoder。 awakeFromNib也没有被调用。
init...is only called once per session
init...每个会话只调用一次
I.e. once per re-compile whenever you make a change in the file. When you change IBInspectable properties, init is NOT called again. However...
即,每当您对文件进行更改时,每次重新编译一次。当您更改 IBInspectable 属性时,不会再次调用 init。然而...
prepareForInterfaceBuilderis called on every property change
prepareForInterfaceBuilder在每次属性更改时调用
It's like having KVO on all your IBInspectables as well as other built-in properties. You can test this yourself by having the your _setupmethod called, first only from your init..method. Changing an IBInspectable will have no effect. Then add the call as well to prepareForInterfaceBuilder. Whahla! Note, your runtime code will probably need some additional KVO since it won't be calling the prepareForIBmethod. More on this below...
这就像在您的所有 IBInspectables 以及其他内置属性上都有 KVO。您可以通过_setup调用您的方法来自己测试这一点,首先只能从您的init..方法中调用。更改 IBInspectable 将不起作用。然后也将调用添加到 prepareForInterfaceBuilder. 哇啦!请注意,您的运行时代码可能需要一些额外的 KVO,因为它不会调用该prepareForIB方法。更多关于这下面...
init...is too soon to draw, set layer content, etc.
init...绘制、设置图层内容等为时过早。
At least with my UIButtonsubclass, calling [self setImage:img forState:UIControlStateNormal]has no effect in IB. You need to call it from prepareForInterfaceBuilderor via a KVO hook.
至少对于我的UIButton子类,调用[self setImage:img forState:UIControlStateNormal]在 IB 中没有影响。您需要从prepareForInterfaceBuilderKVO 挂钩或通过 KVO 挂钩调用它。
- When IB fails to render, it doesn't blank our your component but rather keeps the last successful version.
- 当 IB 无法渲染时,它不会使我们的组件空白,而是保留最后一个成功的版本。
Can be confusing at times when you are making changes that have no effect. Check the build logs.
当您进行无效的更改时,有时可能会令人困惑。检查构建日志。
Tip: Keep Activity Monitor nearby
提示:将活动监视器放在附近
I get hangs all the time on a couple different support processes and they take the whole machine down with them. Apply Force Quitliberally.
我一直挂在几个不同的支持流程上,他们把整台机器都带走了。Force Quit自由申请。
(UPDATE: This hasn't really been true since XCode6 came out of beta. It seldom hangs anymore)
(更新:自从 XCode6 推出测试版以来,这并不是真的。它很少挂起了)
UPDATE
更新
- 6.3.1 seems to not like KVO in the IB version. Now you seem to need a flag to catch Interface Builder and not set up the KVOs. This is ok as the
prepareForInterfaceBuildermethod effectively KVOs all theIBInspectableproperties. It's unfortunate that this behaviour isn't mirrored somehow at runtime thus requiring the manual KVO. See the updated sample code below.
- 6.3.1好像不太喜欢IB版本的KVO。现在您似乎需要一个标志来捕获 Interface Builder 而不是设置 KVO。这是可以的,因为该
prepareForInterfaceBuilder方法有效地 KVO 的所有IBInspectable属性。不幸的是,这种行为在运行时没有以某种方式反映,因此需要手动 KVO。请参阅下面的更新示例代码。
UIButton subclass example
UIButton 子类示例
Below is some example code of a working IBDesignable UIButtonsubclass. ~~Note, prepareForInterfaceBuilderisn't actually required as KVO listens for changes to our relevant properties and triggers a redraw.~~ UPDATE: See point 8 above.
下面是一个工作 IBDesignableUIButton子类的一些示例代码。~~注意,prepareForInterfaceBuilder实际上并不是必需的,因为 KVO 会监听我们相关属性的更改并触发重绘。~~ 更新:见上面的第 8 点。
IB_DESIGNABLE
@interface SBR_InstrumentLeftHUDBigButton : UIButton
@property (nonatomic, strong) IBInspectable NSString *topText;
@property (nonatomic) IBInspectable CGFloat topTextSize;
@property (nonatomic, strong) IBInspectable NSString *bottomText;
@property (nonatomic) IBInspectable CGFloat bottomTextSize;
@property (nonatomic, strong) IBInspectable UIColor *borderColor;
@property (nonatomic, strong) IBInspectable UIColor *textColor;
@end
@implementation HUDBigButton
{
BOOL _isInterfaceBuilder;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self _setup];
}
return self;
}
//---------------------------------------------------------------------
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self _setup];
}
return self;
}
//---------------------------------------------------------------------
- (void)_setup
{
// Defaults.
_topTextSize = 11.5;
_bottomTextSize = 18;
_borderColor = UIColor.whiteColor;
_textColor = UIColor.whiteColor;
}
//---------------------------------------------------------------------
- (void)prepareForInterfaceBuilder
{
[super prepareForInterfaceBuilder];
_isInterfaceBuilder = YES;
[self _render];
}
//---------------------------------------------------------------------
- (void)awakeFromNib
{
[super awakeFromNib];
if (!_isInterfaceBuilder) { // shouldn't be required but jic...
// KVO to update the visuals
@weakify(self);
[self
bk_addObserverForKeyPaths:@[@"topText",
@"topTextSize",
@"bottomText",
@"bottomTextSize",
@"borderColor",
@"textColor"]
task:^(id obj, NSDictionary *keyPath) {
@strongify(self);
[self _render];
}];
}
}
//---------------------------------------------------------------------
- (void)dealloc
{
if (!_isInterfaceBuilder) {
[self bk_removeAllBlockObservers];
}
}
//---------------------------------------------------------------------
- (void)_render
{
UIImage *img = [SBR_Drawing imageOfHUDButtonWithFrame:self.bounds
edgeColor:_borderColor
buttonTextColor:_textColor
topText:_topText
topTextSize:_topTextSize
bottomText:_bottomText
bottomTextSize:_bottomTextSize];
[self setImage:img forState:UIControlStateNormal];
}
@end
回答by zisoft
This answer is related to overriding drawRect, but maybe it can give some ideas:
这个答案与覆盖 drawRect 有关,但也许它可以提供一些想法:
I have a custom UIView class which has complex drawings in drawRect. You have to take care about references which are not available during design time, i.e. UIApplication. For that, I override prepareForInterfaceBuilderwhere I set a boolean flag which I use in drawRect to distinguish between runtime and design time:
我有一个自定义 UIView 类,它在 drawRect 中有复杂的绘图。您必须注意在设计时不可用的引用,即 UIApplication。为此,我覆盖prepareForInterfaceBuilder了在 drawRect 中设置布尔标志的位置,该标志用于区分运行时和设计时:
@IBDesignable class myView: UIView {
// Flag for InterfaceBuilder
var isInterfaceBuilder: Bool = false
override init(frame: CGRect) {
super.init(frame: frame)
// Initialization code
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func prepareForInterfaceBuilder() {
self.isInterfaceBuilder = true
}
override func drawRect(rect: CGRect)
{
// rounded cornders
self.layer.cornerRadius = 10
self.layer.masksToBounds = true
// your drawing stuff here
if !self.isInterfaceBuilder {
// code for runtime
...
}
}
}
An here is how it looks in InterfaceBuilder:
这是它在 InterfaceBuilder 中的样子:


回答by Leszek Szary
You do not have to use drawRect, instead you can create your custom interface in a xib file, load it in initWithCoder and initWithFrame and it will be live rendering in IB after adding IBDesignable. Check this short tutorial: https://www.youtube.com/watch?v=L97MdpaF3Xg
您不必使用 drawRect,而是可以在 xib 文件中创建自定义界面,在 initWithCoder 和 initWithFrame 中加载它,并在添加 IBDesignable 后在 IB 中实时呈现。检查这个简短的教程:https: //www.youtube.com/watch?v=L97MdpaF3Xg
回答by Chris Conover
I think layoutSubviews is the simplest mechanism.
我认为 layoutSubviews 是最简单的机制。
Here is a (much) simpler example in Swift:
这是一个(非常)简单的 Swift 示例:
@IBDesignable
class LiveLayers : UIView {
var circle:UIBezierPath {
return UIBezierPath(ovalInRect: self.bounds)
}
var newLayer:CAShapeLayer {
let shape = CAShapeLayer()
self.layer.addSublayer(shape)
return shape
}
lazy var myLayer:CAShapeLayer = self.newLayer
// IBInspectable proeprties here...
@IBInspectable var pathLength:CGFloat = 0.0 { didSet {
self.setNeedsLayout()
}}
override func layoutSubviews() {
myLayer.frame = self.bounds // etc
myLayer.path = self.circle.CGPath
myLayer.strokeEnd = self.pathLength
}
}
I haven't tested this snippet, but have used patterns like this before. Note the use of the lazy property delegating to a computed property to simplify initial configuration.
我没有测试过这个片段,但以前使用过这样的模式。请注意使用延迟属性委托给计算属性来简化初始配置。
回答by Zack Morris
To elaborate upon Hari Karam Singh's answer, this slideshow explains further:
为了详细说明Hari Karam Singh 的回答,这张幻灯片进一步解释了:
http://www.splinter.com.au/presentations/ibdesignable/
http://www.splinter.com.au/presentations/ibdesignable/
Then if you aren't seeing your changes show up in Interface Builder, try these menus:
然后,如果您没有在 Interface Builder 中看到您的更改,请尝试以下菜单:
- Xcode->Editor->Automatically Refresh Views
- Xcode->Editor->Refresh All Views
- Xcode->Editor->Debug Selected Views
- Xcode->编辑器->自动刷新视图
- Xcode->编辑器->刷新所有视图
- Xcode->Editor->Debug Selected Views
Unfortunately, debugging my view froze Xcode, but it should work for small projects (YMMV).
不幸的是,调试我的视图冻结了 Xcode,但它应该适用于小型项目 (YMMV)。
回答by Jakub Truhlá?
In my case, there were two problems:
就我而言,有两个问题:
I did not implement
initWithFramein custom view:(UsuallyinitWithCoder:is called when you initialize via IB, but for some reasoninitWithFrame:is needed forIBDesignableonly. Is not called during runtime when you implement via IB)My custom view's nib was loading from
mainBundle:[NSBundle bundleForClass:[self class]]was needed.
我没有
initWithFrame在自定义视图中实现:(通常initWithCoder:在您通过 IB 初始化时调用,但由于某些原因initWithFrame:仅需要IBDesignable。当您通过 IB 实现时,不会在运行时调用)我的自定义视图的笔尖是从
mainBundle:加载的[NSBundle bundleForClass:[self class]]。
回答by Alpine
I believe you can implement prepareForInterfaceBuilderand do your core animation work in there to get it to show up in IB.
I've done some fancy things with subclasses of UIButton that do their own core animation layer work to draw borders or backgrounds, and they live render in interface builder just fine, so i imagine if you're subclassing UIView directly, then prepareForInterfaceBuilderis all you'll need to do differently. Keep in mind though that the method is only ever executed by IB
我相信你可以prepareForInterfaceBuilder在那里实现并完成你的核心动画工作,让它在 IB 中显示出来。我已经用 UIButton 的子类做了一些奇特的事情,它们做自己的核心动画层工作来绘制边框或背景,并且它们在界面构建器中实时渲染就好了,所以我想如果你直接继承 UIView,那么prepareForInterfaceBuilder你就是全部需要做不同的事情。请记住,该方法仅由 IB 执行
Edited to include code as requested
编辑以包含要求的代码
I have something similar to, but not exactly like this (sorry I can't give you what I really do, but it's a work thing)
我有类似的东西,但不完全像这样(对不起,我不能告诉你我真正在做什么,但这是一个工作)
class BorderButton: UIButton {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
func commonInit(){
layer.borderWidth = 1
layer.borderColor = self.tintColor?.CGColor
layer.cornerRadius = 5
}
override func tintColorDidChange() {
layer.borderColor = self.tintColor?.CGColor
}
override var highlighted: Bool {
willSet {
if(newValue){
layer.backgroundColor = UIColor(white: 100, alpha: 1).CGColor
} else {
layer.backgroundColor = UIColor.clearColor().CGColor
}
}
}
}
I override both initWithCoderand initWithFramebecause I want to be able to use the component in code or in IB (and as other answers state, you have to implement initWithFrameto make IB happy.
我覆盖了两者initWithCoder,initWithFrame因为我希望能够在代码或 IB 中使用该组件(并且如其他答案所述,您必须实施initWithFrame以使 IB 满意。
Then in commonInitI set up the core animation stuff to draw a border and make it pretty.
然后在commonInit我设置核心动画内容来绘制边框并使其变得漂亮。
I also implement a willSetfor the highlighted variable to change the background color because I hate when buttons draw borders, but don't provide feedback when pressed (i hate it when the pressed button looks like the unpressed button)
我还willSet为突出显示的变量实现了一个来更改背景颜色,因为我讨厌按钮绘制边框,但按下时不提供反馈(我讨厌按下的按钮看起来像未按下的按钮)
回答by Ako
Swift 3macro
斯威夫特 3宏
#if TARGET_INTERFACE_BUILDER
#else
#endif
and class with function which is called when IB renders storyboard
和具有在 IB 呈现故事板时调用的函数的类
@IBDesignable
class CustomView: UIView
{
@IBInspectable
public var isCool: Bool = true {
didSet {
#if TARGET_INTERFACE_BUILDER
#else
#endif
}
}
override func prepareForInterfaceBuilder() {
// code
}
}
IBInspectable can be used with types below
IBInspectable 可用于以下类型
Int, CGFloat, Double, String, Bool, CGPoint, CGSize, CGRect, UIColor, UIImage

