Is there a way for Interface Builder to render IBDesignable views which don't override drawRect:

前端 未结 8 562
不知归路
不知归路 2020-12-02 03:45

I very rarely override drawRect in my UIView subclasses, usually preferring to set layer.contents with pre-rendering images and often employing multiple sublaye

相关标签:
8条回答
  • 2020-12-02 04:23

    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:

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

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

    0 讨论(0)
  • 2020-12-02 04:25

    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.

    0 讨论(0)
  • 2020-12-02 04:26

    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)

    0 讨论(0)
  • 2020-12-02 04:35

    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:

    enter image description here

    0 讨论(0)
  • 2020-12-02 04:36

    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

    0 讨论(0)
  • 2020-12-02 04:40

    In my case, there were two problems:

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

    2. My custom view's nib was loading from mainBundle: [NSBundle bundleForClass:[self class]] was needed.

    0 讨论(0)
提交回复
热议问题