本文原文发表自我的【自建博客】,cnblogs同步发表,格式未经调整,内容以原博客为准
我是前言
个人很主张使用Interface Builder
(以下都简称IB
)来构建程序UI,包括storyboard
和xib
,相比代码更可视和易于修改,尤其在使用AutoLayout的时候,一目了然。
但用了这么久IB之后发现一个很大的槽点,就是IB间很难嵌套混用
,比如一个xib中的view是另一个xib的子view,或者一个storyboard中两个vc都用到了一个xib构建的view等。解决方法一般是代码手动拼接,这就造成了比较混乱的情况。
本文将尝试解决这个问题,实现xib的动态桥接
,并提供一个支持cocoapods
的开源工具类供方便使用。
一张图顶十句话:
实现效果:
黑魔法方法
实现这个功能的关键在于:在ib加载的某个时刻将placeholder
的view动态替换成从xib加载的view,下面的方法就可以做到:
1 |
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder NS_REPLACES_RECEIVER;
|
这个方法很少用到,在NSObject (NSCoderMethods)
中定义,由NSCoder
在decode过程中调用(于-initWithCoder:
之后),所以说就算从文件里decode出来的对象也会走这个方法。
方法后面有NS_REPLACES_RECEIVER
这个宏:
1 |
#define NS_REPLACES_RECEIVER __attribute__((ns_consumes_self)) NS_RETURNS_RETAINED
|
在clang的文档中可以找到对这个编译器属性的介绍
> One use of this attribute is declare your own init-like methods that do not follow the standard Cocoa naming conventions.
所以这个宏主要为了给编译器标识出这个方法可以像self = [super init]
一样使用,并作出合理的内存管理。
So,这个方法提供了一个机会,可以将decode出来的对象替换成另一个对象
动态桥接流程
1 2 3 4 5 6 7 8 9 |
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder
{
self = [super awakeAfterUsingCoder:aDecoder];
// 0. 判断是否要进行替换
// 1. 根据self.class从xib创建真正的view
// 2. 将placeholder的属性、autolayout等替换到真正的view上
// 3. return 真正的view
}
|
流程不难理解,就是有2个小难点:
- 步骤1从xib创建真正的view时也会调用这个方法,会造成
递归
,如何判断 - 迁移
AutoLayoutConstrains
解决递归问题
这个topic全网可能就《这篇文章》有写,本文也是从它发起的,但是发现它的方法并不能解决所有问题(尤其是用storyboard加载xib时),所以换了个思路,采取了设置标志位的方式避免递归调用:
1 2 3 4 5 6 7 8 9 10 11 12 |
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder
{
self = [super awakeAfterUsingCoder:aDecoder];
if (这个类的Loading标志位 -> NO)
{
Loading标志位 -> YES
从xib加载真实的View (这里会递归调用这个函数)
return 真实View
}
Loading标志位 -> NO
return self
}
|
方法有点土,但是有效了,源代码文章后面会给地址。
迁移AutoLayoutConstrains
由于IB在加载AutoLayoutConstrains时的顺序是先加载子View内部的约束,后加载父View上的约束,而我们替换placeholder的时机是:
- placehodler view被创建(只带width,height的自身约束)
- 真正的view被从xib动态加载(带其子view的所有约束)
- placeholder被替换成真的view
- placeholder view在其父View(一直到父父父…View)的约束被创建
所以说,迁移AutoLayout时,只需要把placeholder view的自身约束copy到真实View上就好了
(停顿10s感受下)
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
- (void)replaceAutolayoutConstrainsFromView:(UIView *)placeholderView toView:(UIView *)realView
{
for (NSLayoutConstraint *constraint in placeholderView.constraints)
{
NSLayoutConstraint* newConstraint = [NSLayoutConstraint constraintWithItem:realView
attribute:constraint.firstAttribute
relatedBy:constraint.relation
toItem:nil // Only first item
attribute:constraint.secondAttribute
multiplier:constraint.multiplier
constant:constraint.constant];
newConstraint.shouldBeArchived = constraint.shouldBeArchived;
newConstraint.priority = constraint.priority;
[realView addConstraint:newConstraint];
}
}
|
One more thing,保证AutoLayout生效还要加上下面这句话:
1 |
realView.translatesAutoresizingMaskIntoConstraints = NO;
|
开源项目XXNibBridge
光说方案不给源码还是不地道的,demo放到了我的github上面的XXNibBridge项目,回顾一下上面的关系图:
不得不提到IB命名约定
的最佳实践方案:
将类名作为Cell或者VC的Reusable Identifier
设ReuseIdentifier
一直比较蛋疼,我一般将Cell的类名
作为ReuseIdentifier
(当然,大多数情况我们都会子类化Cell的),写法如:
1 |
[self.tableView registerClass:[XXSarkCell class] forCellReuseIdentifier:NSStringFromClass([XXSarkCell class])];
|
在dequeueCell
的时候同理,这样的好处在于省去了起名的恶心、通过ReuseId可以直接找到Cell类、同时重构Cell类名时ReuseId也不用去再改。
View的xib与View的类名同名 同理
实现了桥接Xib的功能的同时,也简单实现了这个命名约定:
1 2 3 4 5 |
// XXNibBridge.h
+ (NSString *)xx_nibID; // 类名
+ (UINib *)xx_nib; // 返回类名对应nib
+ (id)xx_loadFromNib; // 对应nib的类对象
+ (id/*UIViewController*/)xx_loadFromStoryboardNamed:(NSString *)name; // 返回类名对应的vc
|
所以之后的代码可以这么写:
1 |
[tableView registerNib:[XXSarkView xx_nib] forCellReuseIdentifier:[XXSarkView xx_nibID]];
|
XXNibBridge的使用
Cocoapods安装
1 |
pod 'XXNibBridge', :git => 'https://github.com/sunnyxx/XXNibBridge.git'
|
对于要支持Bridge的类,重载下面的方法:
1 2 3 4 5 6 7 |
#import "XXNibBridge.h"
@implementation XXDogeView
+ (BOOL)xx_shouldApplyNibBridging
{
return YES;
}
@end
|
在父的Xib或Storyboard中拖个UIView进来作为Placeholder,设置为真实Nib的类
保证真实Nib的类名和Nib名相同,记得在Nib中设好Class
Done.
References
http://blog.yangmeyer.de/blog/2012/07/09/an-update-on-nested-nib-loading
http://stackoverflow.com/questions/19816703/replacing-nsview-while-keeping-autolayout-constraints
http://clang-analyzer.llvm.org/annotations.html#attr_ns_consumes_self
原创文章,转载请注明源地址,blog.sunnyxx.com