I very rarely override drawRect in my UIView subclasses, usually preferring to set layer.contents
with 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?
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...
-drawRect
.Setting images on UIButton control states works. Arbitrary layer stacks seem to work if a few things are kept in mind...
initWithFrame:
..not initWithCoder
. awakeFromNib
is also NOT called.
init...
is only called once per sessionI.e. once per re-compile whenever you make a change in the file. When you change IBInspectable properties, init is NOT called again. However...
prepareForInterfaceBuilder
is called on every property changeIt's like having KVO on all your IBInspectables as well as other built-in properties. You can test this yourself by having the your _setup
method 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 prepareForIB
method. More on this below...
init...
is too soon to draw, set layer content, etc.At least with my UIButton
subclass, calling [self setImage:img forState:UIControlStateNormal]
has no effect in IB. You need to call it from prepareForInterfaceBuilder
or via a KVO hook.
Can be confusing at times when you are making changes that have no effect. Check the build logs.
I get hangs all the time on a couple different support processes and they take the whole machine down with them. Apply Force Quit
liberally.
(UPDATE: This hasn't really been true since XCode6 came out of beta. It seldom hangs anymore)
UPDATE
prepareForInterfaceBuilder
method effectively KVOs all the IBInspectable
properties. It's unfortunate that this behaviour isn't mirrored somehow at runtime thus requiring the manual KVO. See the updated sample code below.Below is some example code of a working IBDesignable UIButton
subclass. ~~Note, prepareForInterfaceBuilder
isn't actually required as KVO listens for changes to our relevant properties and triggers a redraw.~~ UPDATE: See point 8 above.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With