I very rarely override drawRect in my UIView subclasses, usually preferring to set layer.contents
with pre-rendering images and often employing multiple sublaye
To elaborate upon Hari Karam Singh's answer, this slideshow explains further:
http://www.splinter.com.au/presentations/ibdesignable/
Then if you aren't seeing your changes show up in Interface Builder, try these menus:
Unfortunately, debugging my view froze Xcode, but it should work for small projects (YMMV).
I think layoutSubviews is the simplest mechanism.
Here is a (much) simpler example 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 haven't tested this snippet, but have used patterns like this before. Note the use of the lazy property delegating to a computed property to simplify initial configuration.
I believe you can implement prepareForInterfaceBuilder
and do your core animation work in there to get it to show up in IB.
I've done some fancy things with subclasses of UIButton that do their own core animation layer work to draw borders or backgrounds, and they live render in interface builder just fine, so i imagine if you're subclassing UIView directly, then prepareForInterfaceBuilder
is all you'll need to do differently. Keep in mind though that the method is only ever executed by IB
Edited to include code as requested
I have something similar to, but not exactly like this (sorry I can't give you what I really do, but it's a work thing)
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 state, you have to implement initWithFrame
to make IB happy.
Then in commonInit
I set up the core animation stuff to draw a border and make it pretty.
I also implement a willSet
for the highlighted variable to change the background color because I hate when buttons draw borders, but don't provide feedback when pressed (i hate it when the pressed button looks like the unpressed button)
This answer is related to overriding drawRect, but maybe it can give some ideas:
I have a custom UIView class which has complex drawings in drawRect. You have to take care about references which are not available during design time, i.e. UIApplication. For that, I override prepareForInterfaceBuilder
where I set a boolean flag which I use in drawRect to distinguish between runtime and design 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
...
}
}
}
An here is how it looks in InterfaceBuilder:
You do not have to use drawRect, instead you can create your custom interface in a xib file, load it in initWithCoder and initWithFrame and it will be live rendering in IB after adding IBDesignable. Check this short tutorial: https://www.youtube.com/watch?v=L97MdpaF3Xg
In my case, there were two problems:
I did not implement initWithFrame
in custom view: (Usually initWithCoder:
is called when you initialize via IB, but for some reason initWithFrame:
is needed for IBDesignable
only. Is not called during runtime when you implement via IB)
My custom view's nib was loading from mainBundle
: [NSBundle bundleForClass:[self class]]
was needed.