xib的动态桥接

本文原文发表自个人【自建博客】,cnblogs同步发表,格式未经调整,内容以原博客为准

 

我是前言

我的很主张使用Interface Builder(如下都简称IB)来构建程序UI,包括storyboardxib,相比代码更可视和易于修改,尤为在使用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个小难点:函数

  • 步骤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 } 

方法有点土,可是有效了,源代码文章后面会给地址。ui

迁移AutoLayoutConstrains

因为IB在加载AutoLayoutConstrains时的顺序是先加载子View内部的约束,后加载父View上的约束,而咱们替换placeholder的时机是:this

  1. placehodler view被建立(只带width,height的自身约束)
  2. 真正的view被从xib动态加载(带其子view的全部约束)
  3. placeholder被替换成真的view
  4. placeholder view在其父View(一直到父父父…View)的约束被建立

因此说,迁移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; 

开源项目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

相关文章
相关标签/搜索