在Storyboard中使用由xib定义的view
前言
在iOS开发中,使用storyboard的某些场景下我们可能希望同时使用xib定义一些可以重复利用的view,并在storyboard中调用。本文将分享一种此类xib bridge的简单实现方式。
实现思路
本方法的思路是将xib的File's Owner所对应的UIView作为placeholderView
,其作用只是在storyboard中起到占位作用,并承载storyboard中与xib自定义view相关的AutoLayout约束,其背景色将被设为[UIColor clearColor]
,并且不显示任何内容。显示自定义内容的任务将交给一个UIView
--contentView
,它将作为placeholderView
的子视图。
为了使在storyboard中作用于placeholderView
的AutoLayout约束能够自动的作用于contentView
,可以向placeholderView
添加NSLayoutRelationEqual
约束,让placeholderView
和contentView
的上下左右四个NSLayoutAttribute
分别完全相等,这样contentView
在storyboard中的的frame将完全和placeholderView相同,从而达到目的。
以上方法同样可以适用于xib的某个子view是另外一个xib的情形。
实现方法
首先进行如下准备工作
- 创建一个xib,用于实现子view的自定义内容,本文中命名为
CoverView.xib
。 - 创建一个
UIView
的子类,作为placeholderView
的基类,用于进行xib bridge相关的添加约束和添加contentView
等工作,本文中命名为XibBridgeBaseView
。 - 创建一个
XibBridgeBaseView
的子类,用于存储xib中自定义内容的相关属性并进行相关操作,本文中命名为CoverView
实现子view的xib文件
首先需要在identity inspector中将xib文件的File's Owner设置成为创建好的CoverView
类
接下来我们就可以在xib中自动创建的UIView
子视图中进行自定义UI了,此时可以在CoverView
类中创建该UIView
子视图(这里命名为contentView
)以及其他UI组件的IBAction
和IBOutlet
等。
实现占位视图的基类
作为placeholderView
的基类,XibBridgeBaseView
中定义了如下的一个方法,用于从nib中载入contentView并添加进占位视图的子视图中,其中XibBridgeBaseView
的initWithCoder:
方法将使用其派生子类的类名作为xib的名字,因此子类和其所对应的xib文件应该使用相同的命名。
@implementation XibBridgeBaseView
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self setupXibBridgeWithPlaceholderViewNibName:NSStringFromClass([self class])];
}
return self;
}
- (void)setupXibBridgeWithPlaceholderViewNibName:(NSString *)placeholderViewNibName {
UIView *contentView =[[[NSBundle mainBundle] loadNibNamed:placeholderViewNibName
owner:self
options:nil] objectAtIndex:0];
[self setBackgroundColor:[UIColor clearColor]];
[self addSubview:contentView];
[self setXibBridgeConstraintsToContentView:contentView];
}
其中- (void)setXibBridgeConstraintsToContentView:(UIView *)contentView
方法主要是实现前文提到的通过向占位视图添加NSLayoutRelationEqual
约束让placeholderView
和contentView
的上下左右四个NSLayoutAttribute
分别完全相等:
@implementation XibBridgeBaseView
- (void)setXibBridgeConstraintsToContentView:(UIView *)contentView {
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[contentView]|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(contentView)]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[contentView]|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(contentView)]];
//为保证AutoLayout生效,必须加上下面这句话
contentView.translatesAutoresizingMaskIntoConstraints = NO;
}
实现占位视图的实际类
作为placeholderView
的实际类,每创建一个需要被桥接的xib时就要创建一个对应的实际类(本文中为CoverView
类),CoverView
中将包含在xib中定义的自定义UI相关的属性和操作:
@interface CoverView : XibBridgeBaseView
@property (weak, nonatomic) IBOutlet UILabel *headerLabel;
@property (weak, nonatomic) IBOutlet UIButton *submitButton;
@property (strong, nonatomic) IBOutlet UIView *contentView;
@implementation CoverView
- (IBAction)submitButtonClicked:(UIButton *)sender {
NSLog(@"Hello World!");
}
因为我们是用父storyboard或者xib来调用placeholderView
的,实际使用中只需要将placeholderView
的实际类继承于基类xibBridgeBaseView
,即可实现桥接功能。该placeholderView
的其他初始化工作可以放在- awakeFromNib
中进行
@implementation CoverView
- (void)awakeFromNib {
self.contentView.backgroundColor = [UIColor clearColor];
[self.contentView.layer setBorderColor:[[UIColor whiteColor] CGColor]];
[self.contentView.layer setBorderWidth:1.0];
}
在父的storyboard或者xib中调用placeholderView
做完以上步奏后,只需在父storyboard或者xib中拖一个UIView
来作为placeholderView
,并在identity inspector中将其class属性设置成为对应的placeholderView
的实际类即可。
结语
在使用如上方法来进行xib桥接的过程中需要注意一下几点使用方式:
- 在父的storyboard或者xib中,只需要对拖入的
placeholderView
添加AutoLayout约束即可 - 在子xib中各个UI组件只需要跟
contentView
建立AutoLayout约束即可 - 对于
placeholderView
的identity inspector相关参数需要在子xib中对File's Owner进行设置 - 对于
placeholderView
的attribute inspector相关参数需要在父storyboard或者xib中进行设置
对于xib桥接问题大神SUNNYXX给出了一个更高端的解决方案,利用到了iOS runtime相关的技术。
本文个人博客地址: http://wty.im/2016/02/29/use-view-defined-by-xib-in-storyboard/
Github: https://github.com/wty21cn/