有没有一种方法使Interface Builder能够渲染不覆盖drawRect的IBDesignable视图:
我很less在我的UIView子类中重写drawRect,通常更喜欢使用预渲染图像设置layer.contents
,并经常使用多个子图层或子视图,并根据input参数来操作这些图层。 有没有办法让IB来渲染这些更复杂的视图堆栈?
感谢@zisoft在prepareForInterfaceBuilder
提供的线索。 有Interface Builder的渲染周期的一些细微差别,这是我的问题的来源,值得注意…
- 确认:你不需要使用
-drawRect
。
在UIButton控制状态设置图像的作品。 任何层堆栈似乎工作,如果有几件事情牢记在心…
- IB使用
initWithFrame:
..不是initWithCoder
。 awakeFromNib
也不会被调用。
-
init...
仅在每个会话中调用一次
即每当您对文件进行更改时重新编译一次。 当您更改IBInspectable属性时,不会再调用init。 然而…
-
prepareForInterfaceBuilder
每调用一次属性都会被调用
就像在所有的IBInspectables以及其他内置的属性上都有KVO一样。 你可以通过调用你的_setup
方法来自己testing,首先从你的init..
方法。 更改IBInspectable将不起作用。 然后添加调用以及prepareForInterfaceBuilder
。 Whahla! 请注意,您的运行时代码可能需要一些额外的KVO,因为它不会调用prepareForIB
方法。 更多关于这个下面…
-
init...
过早绘制,设置图层内容等
至less在我的UIButton
子类中,调用[self setImage:img forState:UIControlStateNormal]
在IB中没有效果。 您需要从prepareForInterfaceBuilder
或通过KVO挂钩调用它。
- 当IB渲染失败时,它不会使我们的组件空白,而是保留最后一个成功的版本。
有时候你正在进行没有任何作用的更改时可能会感到困惑。 检查构build日志。
-
提示:保持附近的活动监视器
我一直挂在两个不同的支持过程,他们把整个机器与他们。 应用 Force Quit
。
(更新:自XCode6发布以来,这并不是真的,它很less挂起)
UPDATE
- 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。
就我而言,有两个问题:
-
我没有在自定义视图中实现
initWithFrame
(通常initWithCoder:
在通过IB初始化时调用,但由于某种原因,只有IBDesignable
需要initWithFrame:
在运行时通过IB实现时不会调用) -
我的自定义视图的笔尖是从
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 } } } }
我重写了initWithCoder
和initWithFrame
因为我希望能够在代码或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