为UIView自定义Xib
一、需求
通过Interface Builder的形式创建Xib,并将其和一个UIView的子类绑定,如何实现?
二、解决
这个问题通过搜索,有大量的答案,大概答案的代码如下:
也就是在你的子类中,在初始化方法initWithFrame、initWithCoder中主动加载一个xib对应的类,作为子view添加到当前的view中
这种方式,会明显产生一个问题,会产生两个相同的自定义View对象
先不说两个View带来的危害,可能导致业务代码的冲突,最关键的是,xib中拖出的reference指向只能在loadnib返回的对象中初始化好,你自定义的对象的指向为空。
这样太坑爹了
这也是下面这个问题产生的原因,想通过修改initwithCoder的返回值为自己主动反序列化xib产生的对象
这种方案我也测试了,测试代码如下:
#import "XibHackInit.h" @implementation XibHackInit - (instancetype)initWithCoder:(NSCoder *)aDecoder { static BOOL alreadyInitMark = NO; if(self = [super initWithCoder:aDecoder]) { if(!alreadyInitMark) { NSString *className = NSStringFromClass([self class]); if([className containsString:@"."]) { className = [[className componentsSeparatedByString:@"."] lastObject]; } alreadyInitMark = YES; UIView *targetView = [[self class] loadFromNib:className]; alreadyInitMark = NO; return (XibHackInit *)(targetView?targetView:self); } } return self; } + (UIView *)loadFromNib:(NSString *)nibName { NSArray* nibViews = [[NSBundle mainBundle] loadNibNamed:nibName owner:nil options:nil]; UIView* nibView = (UIView*)[nibViews objectAtIndex:0]; return nibView; } @end
注意initWithCoder中用了一个BOOL变量,这个变量主要是为了避免产生的递归,loadFromNib方法一调用、又会调用到initWithCoder中,简直就成了一个不可解的死bug
上面的代码是OC实现的,为什么不用swift实现,主要是因为swift中init方法除了返回空值之外,不能返回其他值
这么做的结果是什么呢?程序直接Crash
有没有更优美的方案?
这里的折中方案是,先创建一个容器View,在容器View中加载你想要的子View,这样可以充分考虑到Xib中复用的问题
效果:
除了清晰的View层次之外,还可以在Interface Builder中进行可视化,实现的关键代码如下:
核心代码:
import UIKit @IBDesignable class XibElementContainer: UIView { var elementView:UIView? @IBInspectable var elementNibName:String? override func awakeFromNib() { super.awakeFromNib() xibSetup() } func xibSetup() { guard let view = loadViewFromNib() else { return } view.frame = bounds view.autoresizingMask = [.flexibleWidth, .flexibleHeight] addSubview(view) elementView = view } func loadViewFromNib() -> UIView? { guard let nibName = elementNibName else { return nil } let bundle = Bundle(for: type(of: self)) let nib = UINib(nibName: nibName, bundle: bundle) return nib.instantiate( withOwner: self, options: nil).first as? UIView } override func prepareForInterfaceBuilder() { super.prepareForInterfaceBuilder() xibSetup() elementView?.prepareForInterfaceBuilder() } }
有两个关键字:
@IBDesignable InterfaceBuilder 会尝试对这个类的对象进行可视化,会调用prepareForInterfaceBuilder方法
@IBInspectable InterfaceBuilder 会显示这个字段的输入框,可以在界面上面直接输入值
三、最终代码
代码:https://github.com/liqiushui/CustomXibForUIView