有没有一种方法使Interface Builder能够渲染不覆盖drawRect的IBDesignable视图:

我很less在我的UIView子类中重写drawRect,通常更喜欢使用预渲染图像设置layer.contents ,并经常使用多个子图层或子视图,并根据input参数来操作这些图层。 有没有办法让IB来渲染这些更复杂的视图堆栈?

感谢@zisoft在prepareForInterfaceBuilder提供的线索。 有Interface Builder的渲染周期的一些细微差别,这是我的问题的来源,值得注意…

  1. 确认:你不需要使用-drawRect

在UIButton控制状态设置图像的作品。 任何层堆栈似乎工作,如果有几件事情牢记在心…

  1. IB使用initWithFrame:

..不是initWithCoderawakeFromNib也不会被调用。

  1. init...仅在每个会话中调用一次

即每当您对文件进行更改时重新编译一次。 当您更改IBInspectable属性时,不会再调用init。 然而…

  1. prepareForInterfaceBuilder每调用一次属性都会被调用

就像在所有的IBInspectables以及其他内置的属性上都有KVO一样。 你可以通过调用你的_setup方法来自己testing,首先从你的init..方法。 更改IBInspectable将不起作用。 然后添加调用以及prepareForInterfaceBuilder 。 Whahla! 请注意,您的运行时代码可能需要一些额外的KVO,因为它不会调用prepareForIB方法。 更多关于这个下面…

  1. init...过早绘制,设置图层内容等

至less在我的UIButton子类中,调用[self setImage:img forState:UIControlStateNormal]在IB中没有效果。 您需要从prepareForInterfaceBuilder或通过KVO挂钩调用它。

  1. 当IB渲染失败时,它不会使我们的组件空白,而是保留最后一个成功的版本。

有时候你正在进行没有任何作用的更改时可能会感到困惑。 检查构build日志。

  1. 提示:保持附近的活动监视器

我一直挂在两个不同的支持过程,他们把整个机器与他们。 应用Force Quit

(更新:自XCode6发布以来,这并不是真的,它很less挂起)

UPDATE

  1. 6.3.1似乎不喜欢IB版本的KVO。 现在你似乎需要一个标志来捕捉Interface Builder,而不是设置KVO。 这就好像prepareForInterfaceBuilder方法有效KVO所有IBInspectable属性。 不幸的是,这种行为在运行时不会被镜像,因此需要手动KVO。 请参阅下面的更新示例代码。

UIButton子类的例子

下面是一个工作IBDesignable UIButton子类的一些示例代码。 ~~ 请注意, 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 { _isInterfaceBuilder = YES; [self _render]; } //--------------------------------------------------------------------- - (void)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 

这个答案与重写drawRect有关,但也许可以给出一些想法:

我有一个自定义的UIView类在drawRect中有复杂的图纸。 您必须注意在devise期间不可用的参考,即UIApplication。 为此,我重写了prepareForInterfaceBuilder ,我在drawRect中设置了一个布尔标志来区分运行时和devise时间:

 @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 ... } } } 

这里是它在InterfaceBuilder中的外观:

在这里输入图像说明

您不必使用drawRect,而是可以在xib文件中创build自定义接口,将其加载到initWithCoder和initWithFrame中,并在添加IBDesignable后在IB中进行实时渲染。 检查这个简短的教程: https : //www.youtube.com/watch?v = L97MdpaF3Xg

我认为layoutSubviews是最简单的机制。

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 } } 

我没有testing过这个片段,但是之前使用过这种模式。 请注意使用委托给计算属性的lazy属性来简化初始configuration。

就我而言,有两个问题:

  1. 我没有在自定义视图中实现initWithFrame (通常initWithCoder:在通过IB初始化时调用,但由于某种原因,只有IBDesignable需要initWithFrame:在运行时通过IB实现时不会调用)

  2. 我的自定义视图的笔尖是从mainBundle加载: [NSBundle bundleForClass:[self class]]是需要的。

我相信你可以实现prepareForInterfaceBuilder ,并在那里做你的核心animation工作,让它出现在IB。 我已经用UIButton的子类做了一些奇特的事情,他们自己的核心animation层工作来绘制边界或背景,他们生活在界面生成器渲染就好,所以我想如果你直接prepareForInterfaceBuilder UIView,然后prepareForInterfaceBuilder是所有你需要做不同的事情。 请记住,该方法只能由IB执行

按照要求编辑代码

我有类似的东西,但不完全是这样的(抱歉,我不能给你我真正做的,但这是一个工作的事情)

 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 } } } } 

我重写了initWithCoderinitWithFrame因为我希望能够在代码或IB中使用组件(和其他答案一样,必须实现initWithFrame才能使IB快乐。

然后在commonInit我设置核心animation的东西来画一个边框,使其漂亮。

我也实现了高亮variables的willSet来改变背景颜色,因为我讨厌当button绘制边框,但是当按下时不提供反馈(当我按下button看起来像未按下的button时,我讨厌它)

为了详细说明哈里·卡拉姆·辛格(Hari Karam Singh)的回答 ,这张幻灯片进一步解释说:

http://www.splinter.com.au/presentations/ibdesignable/

然后,如果您没有看到您的更改显示在Interface Builder中,请尝试以下菜单:

  • Xcode-> Editor->自动刷新视图
  • Xcode-> Editor->刷新所有视图
  • Xcode-> Editor->debugging选定的视图

不幸的是,debugging我的视图冻结Xcode,但它应该适用于小项目(YMMV)。

Swift 3macros

 #if TARGET_INTERFACE_BUILDER #else #endif 

以及当IB渲染故事板时调用的函数类

 @IBDesignable class CustomView: UIView { @IBInspectable public var isCool: Bool = true { didSet { #if TARGET_INTERFACE_BUILDER #else #endif } } override func prepareForInterfaceBuilder() { // code } } 

IBInspectable可以使用下面的types

 Int, CGFloat, Double, String, Bool, CGPoint, CGSize, CGRect, UIColor, UIImage