iOS自定义UIView

XIB自定义UIView

在XIB中使用autolayout是非常方便的,所以有的时候我们需要使用XIB来创建自定义UIView。但是不像UITableViewCell,创建Class的时候可以选择勾选自动创建XIB,UIView只能单独创建XIB,下面整理一下通过XIB创建自定义UIView的步骤:

  • 创建自定义的UIView,例如MyXibView extends UIView;
  • 创建不相同名字的XIB,使用View模板,例如MyXib.xib;
  • 在XIB中设置View的Class为MyXibView;
  • 在MyXibView.m中实现一个类方法,代码如下:
+ (MyXibView*) myXibView {
  NSArray* array = [[NSBundle mainBundle] loadNibNamed:@"MyXib" owner:nil options:nil];
  return [array objectAtIndex:0]; 
}

后续的操作就如你正常的进行,在XIB中放置控件,并设置autolayout约束,关联代码文件中的IBOutlet和IBAction,进行相关的业务逻辑处理;

这里XIB与Class的必须要使用不同的名字,否则会出各种问题:

  1. 在XIB的ViewController中frame问题,设置了frame无效,必须要在viewWillLayoutSubviews方法重设。
  2. 在纯代码的ViewController中会奔溃,[UIViewController _loadViewFromNibNamed:bundle:] loaded the "MyView" nib but the view outlet was not set.。猜测原因是[NSBundle loadNibNamed]方法会寻找Nib同名的Class,默认认为这个Class是UIViewController,需要一个IBOutlet,但是这里没有连接IBOutlet,而且对应的也只是一个UIView。

initWithCoder

在测试上面自定义方法的时候我有想到initWithCoder方法,也参考了initWithFrame方法的实现,结果就死循环了,loadNibNamed方法会调用initWithCoder方法,而initWithCoder方法又调用了loadNibNamed方法。

initWithCoder方法是当前控件被其他的XIB或者Storyboard文件引用的时候调用的,自己有一个对应的XIB文件,然而外部的XIB或者Storyboard文件中也有相应的布局,那么这两个布局冲突了。如果不实现initWithCoder方法,XIB或者Storyboard引用的时候自己的XIB中的内容和布局都不会存在。

所以如果需要在外部的XIB或者Storyboard文件中使用自定义的控件,不要使用XIB这种方式创建,应该试用纯代码的方式创建。

参考链接:iPhone-load-a-uiview-from-a-nib-file

IB_DESIGNABLE和IBInspectable

使用纯代码自定义UIView的时候可以使用这两个属性,让使用者在XIB或者Storyboard中实时看到修改属性的效果。

具体使用方法是在Interface上加上IB_DESIGNABLE,在属性上加上IBInspectable,重写属性的setter方法,对UIView的样式进行修改。

#import <UIKit/UIKit.h>

IB_DESIGNABLE
/**
 自定义导航栏
 */
@interface CustomNavi : UIView

/**
 是否需要返回按钮
 */
@property (nonatomic, assign) IBInspectable BOOL needBackButton;
/**
 显示的标题
 */
@property (nonatomic, copy) IBInspectable NSString *title;
/**
 背景色
 */
@property (nonatomic, copy) IBInspectable UIColor *bgColor;

@end

// ------------------------- 我是分割线 ------------------------- 
#import "CustomNavi.h"

@implementation CustomNavi {
    UIButton *_backButton;
    UILabel *_titleLabel;
}

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

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

/**
 初始化,代码创建标题Label
 */
- (void)initUI {
    _titleLabel = [[UILabel alloc] init];
    [self addSubview:_titleLabel];
}


/**
 设置标题,居中显示

 @param title 标题
 */
- (void)setTitle:(NSString *)title {
    [_titleLabel setText:title];
    [_titleLabel sizeToFit];
    CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
    _titleLabel.center = CGPointMake(screenWidth/2.0f, 22);
}

/**
 设置是否需要返回按钮
 */
- (void)setNeedBackButton:(BOOL)needBackButton {
    if (needBackButton) {
        self.backButton.hidden = !needBackButton;
    } else {
        _backButton.hidden = !needBackButton;
    }
}

/**
 懒加载方式创建返回按钮
 */
- (UIButton *)backButton {
    if (_backButton == nil) {
        _backButton = [UIButton buttonWithType:UIButtonTypeCustom];
        _backButton.frame = CGRectMake(8, 6, 28, 28);
        [_backButton setTitle:@"<" forState:UIControlStateNormal];
        [self addSubview:_backButton];
    }
    return _backButton;
}

/**
 设置背景颜色
 */
- (void)setBgColor:(UIColor *)bgColor {
    self.backgroundColor = bgColor;
}

@end

XIB创建UIView方式2

XIB创建UIView除了上面的方法外,还有一种方式。

  1. 创建XIB文件,创建Class文件,两个文件名随意,可以相同也可以不相同。
  2. 选择XIB的File's Owner,设置Class为创建的Class文件名。
  3. 在Class中定义一个IBOutlet的view,将XIB中的View连线至这个IBOutlet。
  4. 在XIB中放置控件,并设置autolayout约束,关联代码文件中的IBOutlet和IBAction。
  5. 重写Class中的init方法:
@interface CustomXibView : UIView

@property (nonatomic, weak) IBOutlet UIView *contentView;

@end

@implementation CustomXibView

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

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

- (void)commonInit {
    // 这里因为与XIB名字相同,所以使用[self class]
    [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil];
    [self addSubview:self.contentView];
    self.contentView.frame = self.bounds;
    // 把内容放到最下面
    [self sendSubviewToBack:self.contentView];
}

参考链接:Creating a Custom View using a xib in Objective-C

这种方式创建的UIView其实外面会套个壳,frame修改的时候需要同步到contentView,但是优点是可以同时在存代码和XIB中使用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容