IOS开发—IB_Designable & IBInspectable介绍

引言

在Xcode的旧版本中,试图创建一个自定义控件,并不是很容易,因为在IB中,并不能实时预览到你的设计成果,只能在模拟器中测试。对于设计一个单一组件,可能需要花费大量时间。
Xcode6的发布,苹果为开发者构建自定义控件推出了新功能IBDesignable和IBInspectable,允许在IB中实时预览设计成果。很明显,这会给实际开发提升很高效率。

IB_Designable

常规用法

时时渲染的“罪魁祸首”!就是它将代码设计成果时时呈现在IB中。废话不多说,通过一个简单的Demo来看看时时渲染的魅力吧!

这是视图控件最终的呈现效果,视图很简单,不用IB_Designable也能轻松搞定,本文旨主要介绍IB_Designable的用法。
首先,新建一个Single View Application工程,然后创建一个自定义视图(一定得是UIView的子类)

在编写代码之前,现在类的定义之前加上宏定义IB_DESIGNABLE,这样才能实现实时渲染。之后,创建一个Xib文件用来实时呈现代码成果(这个Xib可以是目标IB,也可以只作为辅助工具单纯用来显示视图,最后删掉就行了),这里我直接把main.storyboard拿来用了。拖出一个UIView控件到IB中,任意设置一个背景颜色方便查看,然后将该view对象的class设置为刚刚创建的CustomView。

然后选中IB中的视图,查看Editor—-Automatically Refresh Views有没有勾选,这是自动刷新IB的开关,只有勾选了才会实时渲染view到IB。

准备工作完毕,开始编写代码,在编写过程中就会发现,代码成果会实时更新显示在IB中,再也不用Run来查看了。

完整代码代码如下

#import "CustomView.h"

@interface CustomView ()
@property (nonatomic, strong) UIView *backgroundView;
@property (nonatomic, strong) UILabel *labelCustom;
@end

IB_DESIGNABLE
@implementation CustomView

//在运行的时候调用该方法从IB中加载视图
- (instancetype)initWithCoder:(NSCoder *)coder
{
    self = [super initWithCoder:coder];
    if (self) {
        [self setupContentView];
    }
    return self;
}

//在调试渲染的时候调用initFrame方法,传入IB中的frame
- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self setupContentView];
    }
    return self;
}

- (void)setupContentView{
    self.backgroundView = ({
        UIView *view = [[UIView alloc]initWithFrame:CGRectMake(10, 10, 150, 100)];
        view.backgroundColor = [UIColor orangeColor];
        [self addSubview:view];
        view;
    });

    self.labelCustom = ({
        UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(20, 20, 100, 60)];
        label.font = [UIFont systemFontOfSize:20.0];
        label.textAlignment = NSTextAlignmentCenter;
        label.textColor = [UIColor redColor];
        label.backgroundColor = [UIColor greenColor];
        label.text = @"实时渲染";
        [self addSubview:label];
        label;
    });
}
@end

需要注意的是:
1、在调试的时候,触发是的initWithFrame方法,而在运行的时候,如果是直接从IB中加载,而不是通过initWithFrame方法创建,调用的是initWithCoder方法。
2、渲染之前会调用drawRect方法,因此可以重写该方法绘制自定义内容。
3、这里有一个缺点,注意看我在setupContentView设置空间frame的时候直接将控件长宽都写死了,如果想借助self.frame来设置大小,是不可行的(当然结果是可行的,只是调试的时候不可行,因为调试的时候self(视图本身)的frame是未知的)。可以看下借助self.frame的后果:

4、既然在调试过程中无法获取self.frame来设置内容控件的frame,就只能使用自动布局了。个人习惯使用第三方库Mansory来进行代码自动布局,在此不做介绍,读者可自行了解。

Bundle注意点

如果在调试的时候代码中需要获取一张图片,通常是用[UIImage imageNamed:@”imageName”]来获取的。但是调试的时候会发现这样是获取不到图片的。看下面代码及IB中的显示:

#import "CustomView.h"
#import "masonry.h"

@interface CustomView ()
@property (nonatomic, strong) UIImageView *imageView;
@end

IB_DESIGNABLE
@implementation CustomView

//在运行的时候调用该方法从IB中加载视图
- (instancetype)initWithCoder:(NSCoder *)coder
{
    self = [super initWithCoder:coder];
    if (self) {
        [self setupContentView];
    }
    return self;
}

//在调试渲染的时候调用initFrame方法,传入IB中的frame
- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self setupContentView];
    }
    return self;
}

- (void)setupContentView{
    self.imageView = ({
        UIImageView *imageView = [[UIImageView alloc]init];
        imageView.backgroundColor = [UIColor lightGrayColor];
        imageView.contentMode = UIViewContentModeScaleToFill;
        imageView.image = [UIImage imageNamed:@"ali"];
        [self addSubview:imageView];
        imageView;
    });
    //自动布局
    [self.imageView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.mas_equalTo(self).insets(UIEdgeInsetsMake(0, 0, 0, 0));
    }];
}
@end

原因在于IB在渲染和运行时的bundle是不一样的,即获取资源的目录是不一样的。调试时需要到本类所在的目录中去获取资源,目录为[NSBundle bundleForClass:[self class]],而运行时则在主目录获取资源,目录为[NSBundle mainBundle] (运行的时候默认为该目录,需要从主目录获取资源时有bundle参数的地方传nil即可)。
怎么办?解决的办法是针对不同的情况设置不同的目录,再从改目录去获取资源。借助一个预编译指令能帮助我们实现:

#if TARGET_INTERFACE_BUILDER
    //IB调试时调用
#else
    //运行时调用
#endif

最终效果如下:

bingo!
另外不只是图片资源,其他所有涉及bundle的方法都会有这个问题,解放方法就是用上面预编译指令区分调试时和运行时的bundle。当然调试完成后可以将指令删除,只保留运行时调用的代码。

IBInspectable

IBInspectable宏定义是和IB_Designable一起引进的,IB_Designable的作用是试试渲染视图,IBInspectable的作用实现视图的属性的可视化操作。

在Attributes inspector中系统提供了一些视图的可视化操作,我们可以自己将视图自定义的属性设置成可视化操作来替代代码设置。注意必须是KVC属性,包括(Int、CGFloat、Double、String、Bool、CGPoint、CGSize、CGRect、UIColor、UIImage)
方法是在目标属性申明的时候在类型前加上宏定义IBInspectable,例如:

@property (nonatomic, strong) IBInspectable UIImage *image;

这时再来看Attributes inspector,发现多了一项操作属性myImage:

演示代码如下:

#import "CustomView.h"

@interface CustomView ()
@property (nonatomic, assign) IBInspectable CGFloat cornerRadious;
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) IBInspectable UIImage *image;
@end

IB_DESIGNABLE
@implementation CustomView

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self initContentView];
    }
    return self;
}

- (instancetype)initWithCoder:(NSCoder *)coder
{
    self = [super initWithCoder:coder];
    if (self) {
        [self initContentView];
    }
    return self;
}

- (void)initContentView{
    NSLog(@"init ---  %@",self.image);
}

//运行情况下初始化完毕后会调用该方法,在init方法中获取不到attributes inspector中设置的自定义属性的值,在drawRect方法中会获取到。
- (void)drawRect:(CGRect)rect{
    NSLog(@"draw ---  %@",self.image);
    self.imageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 150, 150)];
    self.imageView.backgroundColor = [UIColor lightGrayColor];
    self.imageView.contentMode = UIViewContentModeScaleToFill;
    self.imageView.image = self.image;
    [self addSubview:self.imageView];
}

//调试情况下在Attributes inspector中设置cornerRadious属性会调用队形属性的set方法
- (void)setCornerRadious:(CGFloat)cornerRadious{
    _cornerRadious = cornerRadious;
    self.layer.cornerRadius = _cornerRadious;
}

@end

可视化操作效果:

说明:

1、在attributes inspector中设置属性的值后,会调用对应属性的set方法给属性赋值,可以重写这个方法添加必要的代码。
2、属性的set方法调用完毕之后,调用drawRect方法。
3、在可视化操作调试过程中只调用属性的set方法和drawRect方法,不会调用init方法。因此若想配合IB_DESIGNABLE实时渲染视图,内容视图的代码只能写在drawRect里了。如果写在init方法里会看到即使通过attributes inspector修改了属性值,IB中并没有更新视图,因为init方法更本没调用,怎么刷新视图呢!
4、之前介绍IB_Designable的时候说过调试时实时渲染会调用initWithFrame方法以及drawRect方法,另外运行时也会先调用init方法记忆drawRect方法。如果在attributes inspector中对设置了某属性值,不管是调试还是运行,init方法中都获取不到那个值,得到drawRect方法中才能获取到。
5、调试的时候我在drawRect中用masonry自动布局子控件,竟然无效!不知道为什么。。

综合上述3、4点,个人并不喜欢或者不习惯用IBInspectable,因为drawRect本意是用来绘制视图的,而不是添加控件的,不习惯将添加控件的代码写在drawRect里。

总结

一、IB_Designable
1、IB_Designable能够实时渲染视图到IB中,避免了编码过程中重复Run程序,方法开发。
2、IB调试过程中注意IB目录和运行时的目录不是一回事,调试时要本类目录加载资源,否则调试时无法获取资源。
3、使用IB_Designable调试每次渲染都会先后调用initWithFrame和drawRect,运行时则调用init(initWithFrame或initWithCoder)和drawRect。搞清楚方法的调用顺序便于理清思路以应付遇到的各种问题。

二、IBInspectable
1、IBInspectable用来实现视图KVC属性的可视化操作。
2、调试过程中在attributes inspector中设值只调用属性的set方法和drawRect方法,因此必需在这两个方法中对对应控件的属性赋值才能显示更新。

参考文档:
《使用IB_DESIGNABLE动态查看,修改Masonry纯代码布局》
《在Xcode6中使用IBDesignable创建自定义控件》

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值