Is there a way for Interface Builder to render IBD

2019-01-09 22:31发布

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?

8条回答
ゆ 、 Hurt°
2楼-- · 2019-01-09 23:27

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.

查看更多
姐就是有狂的资本
3楼-- · 2019-01-09 23:31

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)

查看更多
登录 后发表回答