我的很主張使用Interface Builder
(如下都簡稱IB
)來構建程序UI,包括storyboard
和xib
,相比代碼更可視和易於修改,尤爲在使用AutoLayout的時候,一目瞭然。
但用了這麼久IB以後發現一個很大的槽點,就是IB間很難嵌套混用
,好比一個xib中的view是另外一個xib的子view,或者一個storyboard中兩個vc都用到了一個xib構建的view等。解決方法通常是代碼手動拼接,這就形成了比較混亂的狀況。
本文將嘗試解決這個問題,實現xib的動態橋接
,並提供一個支持cocoapods
的開源工具類供方便使用。html
實現這個功能的關鍵在於:在ib加載的某個時刻將placeholder
的view動態替換成從xib加載的view,下面的方法就能夠作到:ios
1 |
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder NS_REPLACES_RECEIVER; |
這個方法不多用到,在NSObject (NSCoderMethods)
中定義,由NSCoder
在decode過程當中調用(於-initWithCoder:
以後),因此說就算從文件裏decode出來的對象也會走這個方法。
方法後面有NS_REPLACES_RECEIVER
這個宏:git
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出來的對象替換成另外一個對象
動態橋接流程
github
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個小難點:函數
遞歸
,如何判斷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 } |
方法有點土,可是有效了,源代碼文章後面會給地址。ui
因爲IB在加載AutoLayoutConstrains時的順序是先加載子View內部的約束,後加載父View上的約束,而咱們替換placeholder的時機是:this
因此說,遷移AutoLayout時,只須要把placeholder view的自身約束copy到真實View上就行了
(停頓10s感覺下)
代碼以下:spa
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生效還要加上下面這句話:
code
1 |
realView.translatesAutoresizingMaskIntoConstraints = NO; |
光說方案不給源碼仍是不地道的,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]]; |
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.
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