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

前端 未结 8 563
不知归路
不知归路 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:41

    Thanks, @zisoft for the clueing me in on prepareForInterfaceBuilder. There a few nuances with Interface Builder's render cycle which were the source of my issues and are worth noting...

    1. Confirmed: You don't need to use -drawRect.

    Setting images on UIButton control states works. Arbitrary layer stacks seem to work if a few things are kept in mind...

    1. IB uses initWithFrame:

    ..not initWithCoder. awakeFromNib is also NOT called.

    1. init... is only called once per session

    I.e. once per re-compile whenever you make a change in the file. When you change IBInspectable properties, init is NOT called again. However...

    1. prepareForInterfaceBuilder is called on every property change

    It's like having KVO on all your IBInspectables as well as other built-in properties. You can test this yourself by having the your _setup method called, first only from your init.. method. Changing an IBInspectable will have no effect. Then add the call as well to prepareForInterfaceBuilder. Whahla! Note, your runtime code will probably need some additional KVO since it won't be calling the prepareForIB method. More on this below...

    1. init... is too soon to draw, set layer content, etc.

    At least with my UIButton subclass, calling [self setImage:img forState:UIControlStateNormal] has no effect in IB. You need to call it from prepareForInterfaceBuilder or via a KVO hook.

    1. When IB fails to render, it doesn't blank our your component but rather keeps the last successful version.

    Can be confusing at times when you are making changes that have no effect. Check the build logs.

    1. Tip: Keep Activity Monitor nearby

    I get hangs all the time on a couple different support processes and they take the whole machine down with them. Apply Force Quit liberally.

    (UPDATE: This hasn't really been true since XCode6 came out of beta. It seldom hangs anymore)

    UPDATE

    1. 6.3.1 seems to not like KVO in the IB version. Now you seem to need a flag to catch Interface Builder and not set up the KVOs. This is ok as the prepareForInterfaceBuilder method effectively KVOs all the IBInspectable properties. It's unfortunate that this behaviour isn't mirrored somehow at runtime thus requiring the manual KVO. See the updated sample code below.

    UIButton subclass example

    Below is some example code of a working IBDesignable UIButton subclass. ~~Note, prepareForInterfaceBuilder isn't actually required as KVO listens for changes to our relevant properties and triggers a redraw.~~ UPDATE: See point 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
    
    0 讨论(0)
  • 2020-12-02 04:47

    Swift 3 macro

    #if TARGET_INTERFACE_BUILDER
    #else
    #endif
    

    and class with function which is called when IB renders 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 讨论(0)
提交回复
热议问题