Thanks, @zisoft for the tip of me at prepareForInterfaceBuilder . There are several nuances with the interface visualization cycle, which was the source of my problems and it is worth noting ...
- Confirmed: you do not need to use
-drawRect .
Configuring images in control states UIButton works. Arbitrary stacks of layers seem to work if a few things are remembered ...
- IB uses
initWithFrame:
initWithCoder . awakeFromNib also NOT called.
init... is called only once per session
those. once per recompile whenever you make changes to a file. When you change the IBInspectable properties, init is not called again. However...
prepareForInterfaceBuilder is called each time the property is changed.
He likes to have KVO on all of your IBInspectables, as well as other built-in properties. You can verify this yourself by calling the _setup method, first only from your init.. method. Changing IBInspectable will have no effect. Then add also the prepareForInterfaceBuilder call. Whahla! Note that your code will probably need some extra KVO at runtime since it will not call the prepareForIB method. More on this below ...
init... too early to draw, set the contents of a layer, etc.
At least with my UIButton subclass UIButton calling [self setImage:img forState:UIControlStateNormal] does not affect IB. You need to call it from prepareForInterfaceBuilder or using the KVO hook.
- When IB fails to render, it does not clear our component, but saves the latest successful version.
It can be confusing when you make changes that do not affect. Check build logs.
Tip. Keep your activity monitor close by
I get freezes all the time on several different support processes, and they take the whole machine with them. Apply Force Quit liberally.
(UPDATE: This is really not the case since Xcode6 is out of beta. It rarely hangs)
UPDATE
- 6.3.1 does not seem to be similar to KVO in version IB. Now you seem to need a flag to catch Interface Builder and not configure KVO. This is normal, since the
prepareForInterfaceBuilder method effectively uses KVO for all IBInspectable properties. Unfortunately, this behavior is not so mirrored at runtime, which requires KVO guidance. See the updated code example below.
UIButton subclass example
The following is sample code for a working IBDesignable UIButton subclass. ~~ Note. prepareForInterfaceBuilder is not really required, as KVO listens for changes to our respective properties and starts the redraw. ~~ UPDATE: see paragraph 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