对于重用xib进行开发的朋友,往往遇到一些很现实的问题,如xib嵌套,无法重用继承等,导致xib的数量泛滥,UI类臃肿等等,后期维护上面工作量比较大。下面实现一种比较简单的xib的多重继承的实现方式及在storyboard中使用xib。
1. xib继承
首先实现xib的加载器
@interfaceNibLoader:NSObject
+ (instancetype)shared;
- (id)loadFrom:(Class)cla;
@end
@implementation NibLoader
- (id)loadFrom:(Class)cla
{
NSString* nib =NSStringFromClass(cla);
UINib* uNib = [nibTableobjectForKey:nib];
idtargetObject =nil;
if(!uNib) {
//存储加载nib的归档,对应频繁加载的视图,采用缓存可以减少加载时间
uNib = [UINibnibWithNibName:nibbundle:nil];
[nibTablesetObject:uNibforKey:nib];
}
//从nib解档
NSArray * ary = [uNib instantiateWithOwner:nil options:nil];
for(idobjectinary) {
if([objectisKindOfClass:cla]) {
targetObject = object;
break;
}
}
returntargetObject;
}
@end
其次实现视图的加载过程的处理,本文实现UIView的分类相关的方法,需注意以下事项:
1. 处理好xib逐层加载和添加的顺序和事件透传的处理(当出现遮挡的时候的响应顺序)。本例中进行hitTest的方法替换来处理消息透传的处理。
2. 处理好父类的变量在子类的访问问题。
@interfaceUIView(loader)
@property(nonatomic,assign)BOOL touchPassThrough;//若该值为YES则忽略该视图的响应事件,传递给下层视图处理。
+ (id)nib_load:(Class)cla;
@end
@implementation UIView(loader)
+ (void)load
{
staticdispatch_once_tonceToken;
dispatch_once(&onceToken, ^{
[selfexchangeMethod:@selector(hitTest:withEvent:)target:@selector(iv_hitTest:withEvent:)];
});
}
+ (void)exchangeMethod:(SEL)ori target:(SEL)target
{
Method mOri = class_getInstanceMethod(self, ori);
Method mTar = class_getInstanceMethod(self, target);
method_exchangeImplementations(mOri, mTar);
}
+ (id)nib_load:(Class)cla
{
staticNibLoader* loader =nil;
if(loader ==nil) {
loader = [NibLoadershared];
}
UIView* target = [loaderloadFrom:cla];
ClasssupperCla = cla;
NSMutableArray * claList = [NSMutableArray new];
while((supperCla = [supperClasuperclass])) {
if(supperCla ==UIView.class) {
break;
}
[claListinsertObject:supperClaatIndex:0];
}
NSArray* selfSubViews = target.subviews;
for(ClasssupperClainclaList) {
UIView*current = [loaderloadFrom:supperCla];
if(current ==nil) {
current = [[supperClaalloc]initWithFrame:target.frame];
}
current.frame= [targetbounds];
//取出父类成员,并加进子类中
unsignedpropertyCount =0;
objc_property_t*properties =class_copyPropertyList(supperCla, &propertyCount);
//(const char *) attri = 0x000000010b0c0cf7 "T@\"BaseView\",&,N,V_baseView"
for(inti =0; i < propertyCount ; i++ ) {
objc_property_tproperty = properties[i];
NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
if(propertyName)
{
//父类控件成员
idval = [currentvalueForKey:propertyName];
[targetsetValue:valforKey:propertyName];
}
}
[targetaddSubview:current];
}
for(UIView* vinselfSubViews) { //还原视图的图层顺序
[target bringSubviewToFront:v];
}
returntarget;
}
- (UIView*)iv_hitTest:(CGPoint)point withEvent:(UIEvent*)event
{
UIView*hitView = [selfiv_hitTest:pointwithEvent:event];
if(hitView ==self&&self.touchPassThrough==YES){
return nil; //该hitView不响应事件,事件则透传下去。
}
return hitView;
}
- (void)setTouchPassThrough:(BOOL)touchPassThrough
{
objc_setAssociatedObject(self, &(@selector(setTouchPassThrough:)),@(touchPassThrough),OBJC_ASSOCIATION_ASSIGN);
}
- (BOOL)touchPassThrough
{
NSNumber* ret =objc_getAssociatedObject(self, &(@selector(setTouchPassThrough:)));
returnret.boolValue;
}
@end
最后则是具体的xib的加载过程了。实现了以下UIView子类的层次:
@interface BaseView :UIView
@property(nonatomic,assign)int a;
@property(nonatomic,strong)IBOutlet UIView * headPhotoView;
@property(nonatomic,strong)IBOutlet UIButton * queryButton;
@end
@interface SecView :BaseView
@property(nonatomic,strong)BaseView *baseView;
@end
@interface SubView :SecView
@property(nonatomic,strong)SecView *secView;
@end
具体的调用方式:
SubView* view = [UIViewnib_load:SubView.class];
[viewiv_setOrigin:CGPointMake(0,64)];
view.baseView.touchPassThrough = YES; //令父级视图的事件可穿透,如手势事件等在父级不响应
view.secView.touchPassThrough = YES; //令父级视图的事件可穿透,如手势事件等在父级不响
[self.viewaddSubview:view];
2. xib在storyboard中重用。
首先先建基于xib实现的UIView的子类MyView如下:
@interface MyView : UIView
@end
@implementation MyView
- (instancetype)initWithCoder:(NSCoder*)aDecoder
{
self= [superinitWithCoder:aDecoder];
if(self) {
UINib * nib = [UINib nibWithNibName:NSStringFromClass(self.class) bundle:nil];
NSArray * ary = [nib instantiateWithOwner:self options:nil];
UIView* subView = ary.firstObject;
[selfaddSubview:subView];
CGRectfr =self.frame;
fr.size= subView.frame.size;
self.frame= fr;
}
return self;
}
- (void)didMoveToWindow
{
if(self.window) {
[self.subviews[0]iv_setSize:self.frame.size]; //使从xib加载进来的视图尺寸保持一致。
}
}
@end
将xib的File's owner关联到MyView,MyView.xib的关联如下:
storyBoard的加载MyView方式如下:
以上详细内容可以查看https://github.com/jolly-wu/XibInHerit.git。
网友评论