Is there a way for Interface Builder to render IBDesignable views that do not override drawRect:

I very rarely redefine drawRect in my UIView subclasses, I usually prefer setting layer.contents with pre-rendering images and often using multiple sublayers or subzones and manipulating them based on input parameters. Is there any way for IB to make these more complex presentation stacks?

+60
objective-c xcode swift interface-builder
Oct 04 '14 at 9:55
source share
9 answers

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

  1. IB uses initWithFrame:

initWithCoder . awakeFromNib also NOT called.

  1. 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...

  1. 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 ...

  1. 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.

  1. 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.

  1. 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

  1. 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 
+128
Oct 05 '14 at 11:26
source share

This answer is related to overriding drawRect, but maybe it can give some ideas:

I have a custom UIView class that has complex drawings in drawRect. You should take care of links that are not available during development, i.e. UIApplication. To do this, I override prepareForInterfaceBuilder , where I set the boolean flag that I use in drawRect to distinguish between runtime and development time:

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

Here's what it looks like in InterfaceBuilder:

enter image description here

+14
Oct 05 '14 at 8:14
source share

You do not need to use drawRect, instead you can create your own interface in the xib file, load it into initWithCoder and initWithFrame, and it will be displayed in real time in IB after adding IBDesignable. Check out this short tutorial: https://www.youtube.com/watch?v=L97MdpaF3Xg

+8
Dec 18 '14 at 17:58
source share

I think layoutSubviews is the easiest mechanism.

Here is an example (much) in 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 have not tested this snippet, but used templates before. Note that using the lazy property delegates the computed property to simplify the initial configuration.

+6
Oct. 16 '14 at 22:47
source share

In my case, there were two problems:

  • I did not implement initWithFrame in the user view: (usually initWithCoder: is called upon initialization via IB, but for some reason initWithFrame: is required for IBDesignable only. It is not called at runtime when implemented through IB)

  • My custom nib view was loaded with mainBundle : [NSBundle bundleForClass:[self class]] .

+4
Oct 01 '16 at 22:00
source share

To discuss Hari Karam Singh in more detail, this slide show explains further:

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

Then, if you do not see that your changes are displayed in the Builder interface, try these menus:

  • Xcode-> Editor-> Automatically Update Views
  • Xcode-> Editor-> Update All Views
  • Xcode-> Editor-> Debug Selected Views

Unfortunately, debugging my view has frozen Xcode, but it should work for small projects (YMMV).

+3
Jul 09 '15 at 18:32
source share

I believe that you can implement prepareForInterfaceBuilder and make your main animation work there so that it appears in IB. I did some fancy things with UIButton subclasses that do their own work with the main animation layer to draw borders or backgrounds, and they perfectly render rendering in the interface builder, so I assume that if you subclass UIView directly, then prepareForInterfaceBuilder is all. what you need to do differently. Keep in mind that the method is only ever executed by IB

Edited to include code upon request

I have something similar, but not quite like that (sorry, I can’t give you what I really do, but this is work)

 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 initWithCoder and initWithFrame because I want to be able to use the component in code or in IB (and as other answers claim, you must implement initWithFrame to make IB happy.

Then in commonInit I set up the main animation material to draw a border and make it beautiful.

I also implement willSet for the selected variable to change the background color, because I hate when buttons draw borders, but don’t give feedback when pressed (I hate when a pressed button looks like a pressed button)

+2
05 Oct '14 at 5:29
source share

Macro Swift 3

 #if TARGET_INTERFACE_BUILDER #else #endif 

and a class with a function that is called when IB creates the storyboard

 @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

 Int, CGFloat, Double, String, Bool, CGPoint, CGSize, CGRect, UIColor, UIImage 
0
Aug 05 '17 at 14:48
source share

Swift 4.2 is tested and working. This is the cleanest solution.

In Xcode, make sure Editor β†’ Automatically refresh views checked.

 import UIKit @IBDesignable class BoxView: UIView { override func prepareForInterfaceBuilder() { super.prepareForInterfaceBuilder() layer.borderColor = borderColor?.cgColor layer.borderWidth = borderWidth layer.cornerRadius = cornerRadius } @IBInspectable var borderColor: UIColor? { didSet { layer.borderColor = borderColor?.cgColor setNeedsLayout() } } @IBInspectable var borderWidth: CGFloat = 0.0 { didSet { layer.borderWidth = borderWidth setNeedsLayout() } } @IBInspectable var cornerRadius: CGFloat = 0.0 { didSet { layer.cornerRadius = cornerRadius setNeedsLayout() } } } 
  1. Create the BoxView.swift file above.
  2. Select UIView in InterfaceBuilder.
  3. In the Identity Inspector select Custom Class β†’ Class to "BoxView" .
  4. In the Attributes Inspector set borderColor , borderWidth and borderRadius .
0
Dec 21 '18 at 20:31
source share



All Articles