xib嵌套
封裝了一個(gè)view,它自帶有xib。想讓它能在其他xib、SB中使用
1.綁定xib的File Owner
(此xib的所有者,管理這個(gè)xib的邏輯、交互等,類似于ViewController對(duì)view的管理)

(這里你可能會(huì)疑問(wèn)為什么不直接綁定view為此類,如下圖。這種做法也可以,但只適用于用代碼創(chuàng)建的場(chǎng)景,如自定義帶xib的UITableViewCell,每個(gè)cell是由代碼創(chuàng)建的。不能內(nèi)嵌到其他xib、SB中使用,后面有詳細(xì)解釋)

2.在view的.m文件中實(shí)現(xiàn)下面代碼就行了
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
[self loadViewFromXib];
}
return self;
}
- (void)loadViewFromXib
{
// 取xib中view的方法一:
UIView *contentView = [[NSBundle bundleForClass:[self class]] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil].firstObject;
// 取xib中view的方法二:
// UINib *nib = [UINib nibWithNibName:NSStringFromClass([self class]) bundle:[NSBundle bundleForClass:[self class]]];
// UIView *contentView = [nib instantiateWithOwner:self options:nil].firstObject;
/*
這里取view有兩種方法,方法一一步完成,方法二拆成了兩步。
方法二的好處在于我們可以保留取出來(lái)的nib,以后直接用nib生成view,不會(huì)像方法一總是會(huì)重復(fù)去取nib。
方法二常見(jiàn)于帶xib的TableViewCell,因?yàn)閏ell會(huì)大量重用,所以方法二取一次nib保存后,以后要用cell只用執(zhí)行它的第二句代碼來(lái)生成view,更加高效
一般情況下,兩個(gè)沒(méi)什么區(qū)別,用哪個(gè)看個(gè)人喜好
*/
contentView.frame = self.bounds;
contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth| UIViewAutoresizingFlexibleHeight;
[self addSubview:contentView];
}
xib可視化
可以讓封裝好的xib,在其他xib或者SB中實(shí)時(shí)預(yù)覽
只需在上面代碼基礎(chǔ)上加入以下三步:
- view的.h中聲明
IB_DESIGNABLE。(聲明此視圖需要支持預(yù)覽)- 要寫
initWithFrame:方法。(xcode預(yù)覽的工具,顯示view時(shí)會(huì)創(chuàng)建這個(gè)View,此時(shí)走的view的這個(gè)初始化方法)- 用
[NSBundle bundleForClass:[self class]]]取對(duì)對(duì)應(yīng)的Bundle。(可能實(shí)時(shí)運(yùn)行的預(yù)覽工具,取資源文件的位置和app實(shí)際運(yùn)行時(shí)不一樣)
完整代碼如下:(以后可直接復(fù)制使用)
.h
IB_DESIGNABLE
@interface CustomView : UIView
@end
.m
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self loadViewFromXib];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
[self loadViewFromXib];
}
return self;
}
- (void)loadViewFromXib
{
// 對(duì)于Bundle不太了解,但這里要用[NSBundle bundleForClass:[self class]]]取對(duì)對(duì)應(yīng)的Bundle,而不是用mainBundle或者nil
UIView *contentView = [[NSBundle bundleForClass:[self class]] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil].firstObject;
contentView.frame = self.bounds;
contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth| UIViewAutoresizingFlexibleHeight;
[self addSubview:contentView];
}
// 順便一提屬性使用
IBInspectable可在xib的右側(cè)的屬性(Attributes inspector)標(biāo)簽欄中看到
@property (nonatomic, assign) IBInspectable BOOL semicircle;///< 始終一般高度的圓角// 使用
IBOutlet可在連接(Connections inspector)標(biāo)簽欄中看到,一般用來(lái)連線到File Owner
@property (nonatomic, weak) IBOutlet id customDelegate;
如果一直沒(méi)顯示出來(lái):
1.類名不能綁定在view上,要綁在File Owner上,否則會(huì)死循環(huán)crash
2.使用下面的【view Debugging】方法
view Debugging
當(dāng)為view增加 IB_DESIGNABLE時(shí),可能經(jīng)常出現(xiàn) error: IB Designables: Failed to render and update auto layout status for UIView (i5M-Pr-FkT): The agent crashed的情況
此時(shí)有兩種調(diào)試方法:
1.在xib或者sb中,選擇自定義視圖,然后選擇 Editor- Debug Selected Views。這會(huì)重新對(duì)這view運(yùn)行 IBDesignableAgentCocoaTouch,可以進(jìn)入視圖的斷點(diǎn)進(jìn)行調(diào)試
2.在 ~/Library/Logs/DiagnosticReports目錄中有命名為 IBDesignablesAgentCocoaTouch_*.crash的崩潰日志,記錄了堆棧信息
預(yù)覽專用方法
預(yù)覽專用方法,程序?qū)嶋H運(yùn)行時(shí)不調(diào)用。
用于在其他xib、SB中預(yù)覽你customView時(shí),配置一些參數(shù)的默認(rèn)值,以便能完整的預(yù)覽到customView效果
/// 預(yù)覽時(shí)調(diào)用,程序?qū)嶋H運(yùn)行時(shí)不調(diào)用
- (void)prepareForInterfaceBuilder
{
self.arrTitle = @[@"預(yù)覽", @"預(yù)覽文案", @"在程序運(yùn)行時(shí)不會(huì)執(zhí)行,預(yù)覽文案預(yù)覽文案", @"預(yù)覽文案,在程", @"預(yù)覽文案,在程序運(yùn)行時(shí)不會(huì)執(zhí)行預(yù)覽文案,在程序運(yùn)行時(shí)不會(huì)執(zhí)行"];
}
xib原理
為什么不先講原理呢?怕嚇跑你們??
xib本身是xml格式的資源文件,上面記錄了什么控件應(yīng)該擺在哪里,控件本身設(shè)置了哪些屬性等,和你的圖片、音視頻差不多,所以本身是無(wú)法"直接"繼承的。
是的,我們可以"間接"達(dá)到繼承的效果,后面會(huì)說(shuō)。
名詞釋義:
xib:xml格式的文件,記錄控件、屬性及其之間位置關(guān)系。(我們?cè)趚code中直接看到的)
nib:xib被加密、序列化后的二進(jìn)制文件(data)。(app打包后,將包拆開(kāi)后可以看到.nib后綴名的文件)
序列化:歸檔(將xib加密成nib的過(guò)程)
反序列化:解檔(將nib解密,提取到內(nèi)存中,成為我們能直接使用的類的過(guò)程)
頂級(jí)對(duì)象:xib面板左側(cè)對(duì)象邊欄中的最外層的對(duì)象,如最外層的view。instantiateWithOwner:和loadNibNamed:owner:方法返回的數(shù)組中,保存的都是頂級(jí)對(duì)象(原諒我表述不清,自己的理解不深)
xib加載流程:
提取nib文件到內(nèi)存中
從Bundle中取出nib文件,為二進(jìn)制文件,加入到內(nèi)存中對(duì)原xib中所有view對(duì)象進(jìn)行解檔
a) 從內(nèi)存中的二進(jìn)制數(shù)據(jù),取出原xib中的各view對(duì)應(yīng)那部分data
b) 通過(guò)調(diào)用initWithCoder:初始化方法,創(chuàng)建原xib中的所有view,將上面的那部分data作為入?yún)魅?br> c) 這里是每個(gè)view進(jìn)行反序列化,將二進(jìn)制文件轉(zhuǎn)為實(shí)際的類。實(shí)際上不需要我們親自來(lái)反序列化,在initWithCoder:方法中調(diào)用[super initWithCoder:coder]即可,系統(tǒng)的根類中已經(jīng)默認(rèn)做好了
d) 注意??:每個(gè)view(包括頂級(jí)對(duì)象view)在xib中綁定的什么類,就會(huì)創(chuàng)建這個(gè)類。例如一個(gè)View沒(méi)有綁定類名,默認(rèn)系統(tǒng)的UIView類,那么實(shí)際就是調(diào)用的[UIView initWithCoder:aData],這個(gè)view解檔完成后就是UIView的實(shí)例;如果一個(gè)View綁定類名為CustomView,那么實(shí)際就是調(diào)用的[CustomView initWithCoder:aData],然后就進(jìn)入到CustomView類中的initWithCoder:方法了,這個(gè)view解檔完成后就是CustomView的實(shí)例。
e) 注意??:在initWithCoder:方法中,不能使用xib、SB連線出來(lái)的屬性,此時(shí)連線的屬性都為nil,因?yàn)楝F(xiàn)在還沒(méi)開(kāi)始關(guān)聯(lián)屬性關(guān)聯(lián)屬性和方法
對(duì)連線到自己類、File Owner、Object中的屬性進(jìn)行弱引用關(guān)聯(lián),并關(guān)聯(lián)事件。然后這三個(gè)地方就可以使用連線過(guò)來(lái)的屬性和響應(yīng)連線的過(guò)來(lái)方法了。(下面會(huì)講怎么連線到這三個(gè)地方)解檔完成
原xib中各個(gè)view解檔完成后,調(diào)用各自的awakeFromNib方法,告訴你xib已經(jīng)完全ok,可以直接使用了?,F(xiàn)在你可以在awakeFromNib方法中,使用xib、SB連線出來(lái)的屬性了。
拓展點(diǎn):xib、SB可以連線到哪些地方
1)連線到自己類
自己的東西(子視圖、手勢(shì)、Object類等),能通過(guò)連線,被自己使用,這沒(méi)毛病。

2)連線到File Owner
自己的所有者使用自己東西,也挺合理。
File Owner一般作為管理xib的地方,像ViewController、包含此xib的視圖

3)連線到Object
Object是任意類,一般是繼承于NSObject的邏輯類,用于處理視圖中的邏輯部分,這就很厲害了。
Object其實(shí)分為Object和External Object兩種,前者xib、SB會(huì)直接在解檔的時(shí)候創(chuàng)建一個(gè),后者則是需要在xib初始化時(shí),傳入的已存在的實(shí)例,不由xib創(chuàng)建。
使用Object很簡(jiǎn)單,直接拖入一個(gè)Object,然后綁定類名,就可以關(guān)聯(lián)屬性及事件了
這里的CustonViewManager類,可以當(dāng)做專門處理CustonView中邏輯的類

使用External Object稍微復(fù)雜點(diǎn),拖入一個(gè)External Object后,需要先綁定類名(class)及標(biāo)識(shí)符(Identifier)
這里的TestViewController是CustonView的File Owner,TestViewControllerManager類可以當(dāng)做是處理TestViewController中邏輯的類

然后在xib初始化時(shí),傳入
External Object的實(shí)例。
最后就可以在TestViewControllerManager中也使用CustonView中的東西啦

所以,例如xib、SB中有一個(gè)button時(shí),我們?nèi)绻麑⑦@個(gè)button連線到這三個(gè)地方,那么在xib解檔后,這三個(gè)地方都能修改這個(gè)button的屬性。
同理,如果將這個(gè)button的點(diǎn)擊事件連線到到這三個(gè)地方,那么這三個(gè)地方都能響應(yīng)button的點(diǎn)擊事件(三個(gè)地方點(diǎn)擊事件的執(zhí)行順序暫時(shí)還沒(méi)測(cè)試過(guò),不過(guò)我覺(jué)得這里的作用是分開(kāi)執(zhí)行不相關(guān)聯(lián)的邏輯,不能太依靠事件的執(zhí)行順序)。
這樣就能給代碼瘦身,封裝出更優(yōu)雅的View。不過(guò)也因?yàn)樘`活,所以使用時(shí)需要謹(jǐn)防矯枉過(guò)正
拓展點(diǎn):將類名綁File Owner和綁頂級(jí)對(duì)象view的區(qū)別
這點(diǎn)文章開(kāi)頭及xib加載流程中都有提到過(guò)。
以CustonView.xib為例:
你如果將頂級(jí)對(duì)象view的類名綁定為CustonView,那么在xib解檔過(guò)程中,對(duì)頂級(jí)對(duì)象view解檔后,返回的就是CustonView這個(gè)實(shí)例。也就是loadNibNamed: owner:self options:這個(gè)方法返回的數(shù)組中,你拿到的就是custonView的實(shí)例
你如果將File Owner的類名綁定為CustonView,那么CustonView類是CustonView.xib的管理者,CustonView.xib只負(fù)責(zé)給CustonView類提供打包好的view。也就是loadNibNamed: owner:self options:這個(gè)方法返回的數(shù)組中,你拿到的就是UIView的實(shí)例
多數(shù)情況下,我們都是綁定File Owner的類名,為什么呢?舉個(gè)栗子??
我封裝了一個(gè)
RedButton的控件,它自帶有RedButton.xib,里面是一些圖片、文案等,且在RedButton.xib中的綁定頂級(jí)對(duì)象view為RedButton。這時(shí)我在mainViewController.xib中需要用到封裝好的RedButton,我就拖一個(gè)button到xib,然后將類名改為RedButton。接下來(lái)我們?cè)谀X海中模擬一下
mainViewController.xib解檔的整個(gè)過(guò)程:
mainViewController.xib開(kāi)始解檔,它要通過(guò)二進(jìn)制數(shù)據(jù)(nib)創(chuàng)建原xib中所有的view,所以調(diào)用每個(gè)view的initWithCoder:方法- 接著RedButton被調(diào)用
initWithCoder:方法,此時(shí)initWithCoder:時(shí)沒(méi)有寫任何代碼的,程序繼續(xù)走,RedButton實(shí)例生成成功。但是最后我們發(fā)現(xiàn),
mainViewController.xib中RedButton這部分是一片空白。這時(shí)我們回想一下就會(huì)發(fā)現(xiàn),mainViewController.xib只是會(huì)通過(guò)initWithCoder:方法創(chuàng)建了RedButton這個(gè)類,單純的創(chuàng)建這個(gè)類,并不會(huì)加載RedButton.xib這個(gè)文件,所以RedButton缺失了視圖元素。那么我們將代碼改下,在
initWithCoder:中加載RedButton.xib,將其頂級(jí)對(duì)象view作為自己子視圖:
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
[self loadViewFromXib];
}
return self;
}
- (void)loadViewFromXib
{
UINib *nib = [UINib nibWithNibName:NSStringFromClass([self class]) bundle:[NSBundle bundleForClass:[self class]]];
UIView *contentView = [nib instantiateWithOwner:self options:nil].firstObject;
contentView.frame = self.bounds;
[self addSubview:contentView];
}
重新運(yùn)行程序后,會(huì)發(fā)現(xiàn)程序crash了,堆棧信息中顯示陷入了死循環(huán)。我們重新理一下現(xiàn)在的調(diào)用順序:
mainViewController.xib開(kāi)始解檔,它要通過(guò)二進(jìn)制數(shù)據(jù)(nib)創(chuàng)建原xib中所有的view,所以調(diào)用每個(gè)view的initWithCoder:方法- 接著RedButton要開(kāi)始解檔,通過(guò)調(diào)用
[RedButton initWithCoder:aData],此時(shí)initWithCoder:中要加載RedButton.xibRedButton.xib開(kāi)始解檔,調(diào)用每個(gè)view的initWithCoder:方法,包括頂級(jí)對(duì)象view。- 因?yàn)轫敿?jí)對(duì)象view的類名綁定的是RedButton,頂級(jí)對(duì)象view開(kāi)始解檔,調(diào)用
[RedButton initWithCoder:aData],果然進(jìn)入了死循環(huán)這要怎么解決呢?將xib中
綁頂級(jí)對(duì)象view的類名改為綁File Owner的類名就行了。
這說(shuō)明封裝的帶xib的view,如果要支持嵌套在其他xib中,則不能綁定頂級(jí)對(duì)象view的類名,只能通過(guò)綁定File Owner的類名,將RedButton.xib作為單純的視圖元素加到自己的view上。
這也就是為什么綁File Owner的類名的做法更常見(jiàn)。那不能將封裝的帶xib的view,綁頂級(jí)對(duì)象view的類名么?可以的,上面代碼不變,xib綁頂級(jí)對(duì)象view的類名不變。不過(guò)只能用于代碼創(chuàng)建xib的場(chǎng)景,如VC中,用代碼創(chuàng)建自定義view(
RedButon *btn = [self loadNibNamed:@"RedButton" owner:self options:].firstObject),又例如在UITableView的代理中,創(chuàng)建帶xib的Cell。
結(jié)論:在xib中
綁頂級(jí)對(duì)象view的類名:只能用于代碼創(chuàng)建此類
綁File Owner的類名:代碼創(chuàng)建此類、嵌套到其他xib中都可以(常用)
xib繼承
以聊天頁(yè)面為例(如QQ),里面的每條消息都是一行cell,大致有這幾種cell:文字消息行、圖片消息行、視頻消息行、紅包消息行。這些cell都有共同的部分,就是左右的人物頭像,以及氣泡背景。
一般第一反應(yīng)就是做成繼承的類型,父類cell中有共同部分,子類cell中只有各自差異化的東西,如父類cell有頭像、氣泡,文字消息子cell中只有文字,圖片消息子cell中只有圖片。
具體怎么做呢?別急,還需要了解兩個(gè)東西:
1.子類xib中的元素,可以連線到父類中。不管是綁頂級(jí)對(duì)象view還是綁File Owner。
這里舉例不當(dāng),沒(méi)必要同時(shí)連線到父類和子類中,因?yàn)樽宇悤?huì)繼承父類中的屬性,希望別被我誤導(dǎo)了。

2.我一般使用UITableView時(shí)會(huì)開(kāi)始就注冊(cè)cell,這樣就不用在- tableView: cellForRowAtIndexPath:中判斷cell是不是存在,因?yàn)樽?cè)了cell后,然后從重用隊(duì)列中取cell時(shí),如果取不到系統(tǒng)會(huì)自動(dòng)創(chuàng)建一個(gè)新的cell返回給你。
下面是重點(diǎn):
注冊(cè)cell有兩種方式:
1.使用[_tableView registerNib: forCellReuseIdentifier:]方法: 通過(guò)nib創(chuàng)建cell
用于創(chuàng)建自帶xib的cell,也就是cell.xib中,必須綁定頂級(jí)對(duì)象的類名為當(dāng)前cell。
創(chuàng)建時(shí),會(huì)走cell的initWithCoder:方法,不會(huì)走別的初始化方法2.使用
[_tableView registerClass: forCellReuseIdentifier:]方法: 通過(guò)類名來(lái)創(chuàng)建cell
一般用于創(chuàng)建沒(méi)有xib的cell。如果cell有xib,也要使用這種注冊(cè)方法怎么辦,在cell.xib中,綁定File Owner為當(dāng)前cell。
創(chuàng)建時(shí),會(huì)走cell的initWithStyle: reuseIdentifier:方法,不會(huì)走別的初始化方法
現(xiàn)在我們就可以試著想怎么實(shí)現(xiàn)上面說(shuō)的聊天信息cell的繼承:
前提:代碼上,子類cell都繼承父類cell,如文字消息cell、圖片消息cell、視頻消息cll等,都繼承自父類消息cell
那么,父類cell是否擁有xib、父類或子類cell的xib中的內(nèi)容是完整的還是一部分、這些xib是通過(guò)registerNib的方式注冊(cè)還是通過(guò)registerClass的方式注冊(cè),這些組合起來(lái)就有很多可能。
方案一:
Demo: 方案一Demo
父類和子類都有各自的xib,父類的xib是公共部分,子類的xib是差異部分。(xib全部綁定的是File Owner)
子類通過(guò)registerClass注冊(cè), 在初始化時(shí),先加載父類xib中內(nèi)容,addSubView到cell上。然后加載自己xib,addSubView到指定區(qū)域(氣泡)中。

先注冊(cè)cell (一定要使用registerClass的方式注冊(cè),不能是registerNib)
// 首先使用registerClass的方式注冊(cè)子類cell
[_tableView registerClass:[TextChatCell class] forCellReuseIdentifier:NSStringFromClass([TextChatCell class])];
[_tableView registerClass:[ImageChatCell class] forCellReuseIdentifier:NSStringFromClass([ImageChatCell class])];
// 然后就可以用重用獲取cell了
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSDictionary *dict = _arrData[indexPath.row];
NSString *type = dict[@"type"];
if ([type isEqualToString:@"text"]) {
// text
TextChatCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([TextChatCell class]) forIndexPath:indexPath];
return cell;
} else {
// image
ImageChatCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([ImageChatCell class]) forIndexPath:indexPath];
return cell;
}
}
父類cell實(shí)現(xiàn)
@implementation BaseChatCell
#pragma mark - Life Circle
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
[self loadViewFromXib];
}
return self;
}
/// 加載父類xib
- (void)loadViewFromXib
{
UINib *nib = [UINib nibWithNibName:NSStringFromClass([BaseChatCell class]) bundle:[NSBundle mainBundle]];
UIView *contentView = [nib instantiateWithOwner:self options:nil].firstObject;
[self.contentView addSubview:contentView];
// 添加約束,讓內(nèi)容充滿cell
contentView.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[contentView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(contentView)]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[contentView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(contentView)]];
}
@end
子類cell實(shí)現(xiàn)(這里以文字消息cell為例,其他子類cell中代碼一模一樣)
@implementation TextChatCell
#pragma mark - Life Circle
- (void)loadViewFromXib
{
// 加載父類xib中內(nèi)容
[super loadViewFromXib];
// 加載當(dāng)前類xib中內(nèi)容
[super loadChildViewFromXib];
}
/// 加載子類xib
- (void)loadChildViewFromXib
{
// 加載子類xib,將差異化的視圖元素,加載到父視圖指定區(qū)域內(nèi)
UINib *nib = [UINib nibWithNibName:NSStringFromClass([self class]) bundle:[NSBundle mainBundle]];
UIView *contentView = [nib instantiateWithOwner:self options:nil].firstObject;
[self.viewContent addSubview:contentView];
// 添加約束,讓內(nèi)容充滿cell
contentView.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[contentView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(contentView)]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[contentView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(contentView)]];
// 做一些處理,其實(shí)這些也可以在xib直接設(shè)置好
contentView.backgroundColor = [UIColor clearColor];
self.viewContent.backgroundColor = [UIColor clearColor];
}
@end
因?yàn)槊總€(gè)子類中的loadChildViewFromXib方法實(shí)現(xiàn)都是一樣的,所以我們可以把這個(gè)方法提取出來(lái),放到父類中,子類直接調(diào)用,免去了每次都在子類中實(shí)現(xiàn)一遍
@implementation BaseChatCell
#pragma mark - Life Circle
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
[self loadViewFromXib];
}
return self;
}
/// 加載父類xib
- (void)loadViewFromXib
{
UINib *nib = [UINib nibWithNibName:NSStringFromClass([BaseChatCell class]) bundle:[NSBundle mainBundle]];
UIView *contentView = [nib instantiateWithOwner:self options:nil].firstObject;
[self.contentView addSubview:contentView];
// 添加約束,讓內(nèi)容充滿cell
contentView.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[contentView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(contentView)]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[contentView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(contentView)]];
}
/// 加載子類xib
- (void)loadChildViewFromXib
{
// 加載子類xib,將差異化的視圖元素,加載到父視圖指定區(qū)域內(nèi)
UINib *nib = [UINib nibWithNibName:NSStringFromClass([self class]) bundle:[NSBundle mainBundle]];
UIView *contentView = [nib instantiateWithOwner:self options:nil].firstObject;
[self.viewContent addSubview:contentView];
// 添加約束,讓內(nèi)容充滿cell
contentView.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[contentView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(contentView)]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[contentView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(contentView)]];
// 做一些處理,其實(shí)這些也可以在xib直接設(shè)置好
contentView.backgroundColor = [UIColor clearColor];
self.viewContent.backgroundColor = [UIColor clearColor];
}
@end
@implementation TextChatCell
#pragma mark - Life Circle
- (void)loadViewFromXib
{
// 加載父類xib中內(nèi)容
[super loadViewFromXib];
// 加載當(dāng)前類xib中內(nèi)容
[super loadChildViewFromXib];
}
@end
方案二:
父類沒(méi)有xib,每個(gè)子類xib,都是完整的內(nèi)容。(xib全部綁定的是頂級(jí)對(duì)象view)
子類通過(guò)registerNib注冊(cè)cell。

先注冊(cè)cell(這里注冊(cè)方式與方案一相反,要用registerNib的方式注冊(cè),不能是registerClass)
// 首先使用registerNib的方式注冊(cè)子類cell
[_tableView registerNib:[UINib nibWithNibName:NSStringFromClass([CustomerServiceTextCell class]) bundle:nil] forCellReuseIdentifier:NSStringFromClass([CustomerServiceTextCell class])];
[_tableView registerNib:[UINib nibWithNibName:NSStringFromClass([CustomerServiceImageCell class]) bundle:nil] forCellReuseIdentifier:NSStringFromClass([CustomerServiceImageCell class])];
// 然后就可以用重用獲取cell了
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CustomerServiceTextCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([CustomerServiceTextCell class]) forIndexPath:indexPath];
}
然后就沒(méi)了??,方案二的沒(méi)有代碼上需要處理的,唯一要注意的點(diǎn)就是子類xib中共同的視圖元素,都可以連線到父類中,然后就在父類的代碼中處理公共的邏輯。
結(jié)論:
方案一:方便以后的拓展、易維護(hù)。雖然理解上可能比方案二稍稍麻煩點(diǎn),但多用幾次就好了,推薦。
方案二:很來(lái)很簡(jiǎn)單,適合剛接觸xib的人,但強(qiáng)烈不推薦。你日后維護(hù)起來(lái)會(huì)很崩潰的,只要公共部分有改動(dòng),就需要去每個(gè)子類xib中修改,而且這里面一堆約束,很容易出錯(cuò)的。
當(dāng)然還有其他組合出來(lái)的方案,但就我所知的情況中,和這兩種都是大同小異,有興趣的可以研究下其他的搭配。