引言
這兩天突然想起之前看到過有的項目中用到了object, 好奇心起,就順便回溯下xib的使用吧
xib優(yōu)缺點分析
1.有一定的學(xué)習(xí)成本
2.沒有代碼表達(dá)清晰
3.出錯不易發(fā)現(xiàn),無法調(diào)試,尤其是“連線”出了問題
4.文件易沖突稚失,且難解決雨席,不利于團(tuán)隊合作,尤其是在團(tuán)隊中用SB
5.執(zhí)行效率沒有代碼高
6.有時不利于封裝
7.靈活性不強(qiáng),尤其界面靈活多變, 高度不定
8.使用富文本卡頓現(xiàn)象明顯
優(yōu)點
1.開發(fā)效率高
2.減少大量膠水代碼
3.通過xib可以快速逛裤、高效的學(xué)習(xí)控件
4.適配性明顯優(yōu)于代碼(auto layout、size classes)
5.可視化
6.在一定程度上修改方便
VC View 使用xib
1 創(chuàng)建方法
VC在創(chuàng)建的時候可以之間勾選創(chuàng)建 xib
View不能夠直接勾選創(chuàng)建,需要command+N選擇View創(chuàng)建一個xib,一般來說,我們需要創(chuàng)建一個相對應(yīng)的類, 在右邊欄第三個選項(show the identity inspector)下面的custom class-> class中填寫你要與該xib綁定的UIView子類的名字
2 初始化方法
VC加載xib的方法
我們的父類在初始化的時候去自動幫我們找與之對應(yīng)的xib文件,那么問題來了绝葡,父類怎么知道我有沒有xib文件呢?是這樣腹鹉,父類會判斷有沒有和我們這個要初始化的VC相同名字的xib文件藏畅,如果有就會加載該xib文件,如果沒有功咒,父類就認(rèn)為我們該VC沒有xib文件墓赴,就會走正常的init方法竞膳。
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil;
如果一旦我們的VC類的名字與對應(yīng)的xib文件名字不同的時候,我們就必須調(diào)用這個初始化方法來創(chuàng)建VC實例了诫硕;
[[ViewController alloc] initWithNibName:@"xxx" bundle:[NSBundle mainBundle]]
xxx填寫xib文件的名字, 名字不同也是可以的
2 view加載xib
TestView *tView = [[NSBundle mainBundle] loadNibNamed:@"TestView" owner:self options:nil].firstObject;
上述代碼再次說明xib文件是資源文件坦辟,放在main bundle中,@"TestView"是xib文件的名稱章办,后面兩個參數(shù)暫時不用了解锉走,就固定傳self和nil就行,值得說的是藕届,loadNibNamed: owner: options方法返回的是一個數(shù)組挪蹭,而不直接是對象,這是考慮到了Mac開發(fā)會有多個對象返回的情況休偶,在iOS開發(fā)中就只有一個梁厉,固定取第一個就好。
注:一般的UIView對象踏兜,代碼初始化的時候都會調(diào)用initWithFrame:方法词顾,但是用xib創(chuàng)建的UIView對象是不會調(diào)用此方法的,因為該對象的Frame在xib文件中就可以確定了碱妆。以xib的形式保存控件對象的過程其實叫做固化(archive)肉盹,通過xib文件創(chuàng)建控件的過程叫做解固(unarchive),固化是iOS持久化的一種比較好的解決方案疹尾,以后有機(jī)會會說說iOS持久化的各種方式的優(yōu)劣上忍,這里不再深入,而與固化相關(guān)的初始化函數(shù)是:
- (instancetype)initWithCoder:(NSCoder *)aDecoder
所以纳本,當(dāng)以xib創(chuàng)建UIView對象的時候這個函數(shù)會調(diào)用窍蓝,之前在initWithFrame:中要做的事情,可以放在initWithCoder:中繁成,或者放在:
- (void)awakeFromNib
{
[super awakeFromNib];
//...
}
SB文件的使用
由于SB文件與VC一般是一對多的關(guān)系它抱,所以我們不僅要知道即將創(chuàng)建的這個VC的實例對象是加載的哪個SB,而且還要知道加載的是該SB中的哪個具體的VC
SecVC *secVC = [[UIStoryboard storyboardWithName:@"Demo" bundle:[NSBundle mainBundle]] instantiateViewControllerWithIdentifier:@"SecVC"];
有的項目有多個sb進(jìn)行跳轉(zhuǎn), 一般來說需要找到那個sb的初始入口那個VC
SecVC *secVC = [[UIStoryboard storyboardWithName:@"Demo" bundle:[NSBundle mainBundle]] instantiateInitialViewController];
沖突解決
xib朴艰、SB文件有兩種查看方式,當(dāng)文件沖突的時候普通的interface builder方式打開文件會失敗观蓄。
此時右鍵該xib或SB文件,選擇source code方式打開文件祠墅,之后解決沖突的思路與解決project.pbxproj文件沖突的方式是一樣的侮穿,文件中全局搜索<<<<或>>>>定位到錯誤位置,根據(jù)實際情況毁嗦,刪除一個版本的代碼亲茅,保留另一個版本的代碼,直到文件中所有沖突都解決掉。
File Owner
Files Owner指這個xib文件的所屬文件是誰克锣,簡單的說是xib文件和誰建立起交互茵肃,用戶通與該xib呈現(xiàn)的頁面進(jìn)行交互的時候,誰來處理背后的邏輯袭祟。具體來講xib文件能拖動“連線”到哪個源文件中去建立IBAction验残、IBOutlet、delegate巾乳、datasource等您没。
一般基于View創(chuàng)建的xib的Files Owner都指定為一個VC(但一般應(yīng)用都會創(chuàng)建一個相對應(yīng)的類,然后指向該類)〉ò恚基于VC創(chuàng)建的xib氨鹏,創(chuàng)建的時候系統(tǒng)就已經(jīng)把該xib文件的Files Owner指向了該VC,一般這種情況就不對Files Owner做修改了压状。
IBAction
建立IBAction連線的方法:
1.選中需要連線的對象仆抵,按住control鍵,拖動該控件到Files Owner類的@implementatio中松手种冬,填寫方法名即可镣丑。
建立IBAction連線的方法:
1.選中需要連線的對象,按住control鍵碌廓,拖動該控件到Files Owner類的@implementatio中松手,填寫方法名即可剩盒。
2.先在@implementatio中定義一個方法谷婆,在返回值中寫IBAction,然后點擊前面的空心圓辽聊,拖動到xib的對象上纪挎,兩者就建立了“連線”的關(guān)系。
IBOutlet
也是“連線”的一種跟匆,用于標(biāo)記屬性或變量异袄,此方法將Files Owner中的屬性或字段,與xib中的某個對象通過“連線”建立起關(guān)系玛臂。
IBOutlet建立“連線”與查看“連線”的方法與IBAction相同烤蜕。但要注意的是:如果拖動“線”到@interface里,就生成屬性迹冤,如果拖動到@interface{}里或者拖動“線”到@implementatio中的{}里就生成變量讽营。
這里簡單說一下:在@implementation中{}里寫變量(一般不這樣干)和在@interface XXClass()(匿名Category)里的{}中寫變量是一樣的,都是匿名的泡徙。
不怎么用卻實用的
基于View創(chuàng)建的xib橱鹏,是可以“連線”到自己View所在類中的,如果給該xib設(shè)置了Files Owner的屬性后,可以同時“連線”到Files Owner的類中莉兰。
我們可能會遇到這種情況:要封裝一個View為一個單獨的類挑围,該View是可交互的,點擊后糖荒,要發(fā)生變化杉辙,同時又要把交互的事件傳遞給VC,如果用代碼的話寂嘉,就要把事件從View類傳遞給VC類奏瞬,而如果你封裝的類用了xib,那么事情就簡單多了泉孩,同時“連線”到View類和自己Files Owner類對應(yīng)的VC中硼端,在View類中處理UI的變化,在VC中處理邏輯寓搬,而不需要任何事件的傳遞珍昨,當(dāng)用戶交互的時候,這兩根“連線”都會被回調(diào)句喷。
SB使用
xib可以基于View镣典、VC甚至自己獨立的使用,而SB只能基于VC使用唾琼,為什么說比xib更加強(qiáng)大呢兄春?主要是下面的兩個原因:
1.SB支持segue
2.SB對cell的支持更加強(qiáng)大
)]
我們在一個VC中選擇要發(fā)生跳轉(zhuǎn)的按鈕,按control拖動到另一個VC上就會出現(xiàn)一個菜單锡溯,在菜單上你就可以選擇跳轉(zhuǎn)的方式push赶舆、present,這樣不用寫一行代碼就能完成頁面間的跳轉(zhuǎn)祭饭,而兩個VC之間的像紐扣一樣用線連著兩個VC的的東西就是segue芜茵,是一個UIStoryboardSegue對象,我們可以簡單的理解成是完成頁面跳轉(zhuǎn)相關(guān)功能的一個類倡蝙,是不是很簡單九串?
segue雖然簡單,如何傳參寺鸥?
我們知道用SB的segue來實現(xiàn)也頁面跳轉(zhuǎn)十分方便猪钮,但是如果要向跳轉(zhuǎn)的頁面?zhèn)鲄⒃撛趺崔k?
假設(shè)我們要點擊ViewController這個VC里的一個按鈕胆建,跳轉(zhuǎn)到SecVC這個VC中躬贡,把testTitle這個參數(shù)傳過去。第一步還是要選中按鈕眼坏,拖到SecVC里拂玻,然后選中這個segue酸些,給它一個id值,確保用代碼可以找到它檐蚜,然后在ViewController中編碼:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:@"sec"]) {
SecVC *secVC = (SecVC *)segue.destinationViewController;
secVC.testTite = @"hello world";
}
}
設(shè)置SB中segue的identifier魄懂,主要就是要在這里使用,因為多個button發(fā)生的多個頁面跳轉(zhuǎn)都是回調(diào)這個函數(shù)闯第,所以要用identifier來區(qū)分是哪個segue市栗,跳哪個頁面,傳哪些參數(shù)咳短。
不確定的跳轉(zhuǎn)如何使用segue?
一下場景經(jīng)常出現(xiàn):甲VC中有個button咙好,點擊后有可能跳轉(zhuǎn)到乙頁面篡腌,也可能跳轉(zhuǎn)的丙頁面,怎么解勾效?
我們假設(shè)我們要點擊ViewController這個VC里的一個按鈕嘹悼,有可能要跳到SecVC,有可能要跳thirdVC层宫。
1.在SB中準(zhǔn)備segue杨伙。
注意我們這次創(chuàng)建的segue的方式并不是選中button按control拖到另一個VC中,而是選中VC萌腿,右鍵選中triggered segues里manual后面的+拖動到另一個VC里限匣,這個很重要,而且還是要給segue一個id毁菱。
更靈活的segue
如果你熟練掌握了segue他其實可以做很多事情米死,其實segue并不僅僅能完成跳轉(zhuǎn),還有一類segue叫做relationship segue鼎俘,之前是幫助大家理解哲身,所以說的比較簡單辩涝,其實我們選中一個NavigationController按control鍵拖動到另一個VC上的話也可以生成segue贸伐,我們選中root view controller,就將Nav的rootViewController屬性設(shè)置成了這個VC怔揩,這個segue就是relationship segue捉邢,同樣,拖動一個tabbarController也會出現(xiàn)viewControllers的relationship segue商膊。
更好的使用Cell
用xib的時候伏伐,要想用cell,通常是建立一個cell子類晕拆,一個與之對應(yīng)的.xib文件藐翎。用SB的時候要想用cell就簡單的多了,因為cell是可以直接拖動到tableView里的,直接在tableView里管理cell設(shè)置屬性吝镣,當(dāng)然如果是復(fù)雜的cell還是要子類話對應(yīng)的.h堤器、.m的。
SB在使用cell分兩種情況:1靜態(tài)cell末贾,2動態(tài)cell闸溃。
靜態(tài)cell
注意靜態(tài)cell一定要基于UITableViewController,否則會報錯拱撵。
展示靜態(tài)cell的tableView是不用調(diào)用自己必須實現(xiàn)的datasource協(xié)議的
你會很驚訝辉川,為什么必須調(diào)用的datasource協(xié)議都不用實現(xiàn)?答案是靜態(tài)拴测,靜態(tài)的cell乓旗,意義就在于你在xib中設(shè)置成什么樣,他就展現(xiàn)什么樣昼扛,不會再調(diào)用datasource向你要cell了寸齐,因為在SB文件中已經(jīng)確定下來了。
在做一些“死”頁面的時候SB的靜態(tài)cell是很好的選擇抄谐,靜態(tài)cell也不是什么都不能做渺鹦,靜態(tài)cell里的button還是可以拖到@implementation中形成IBAction的,但是是無法生成IBOutlet屬性或字段的蛹含。
即使你強(qiáng)行的給一個靜態(tài)的cell指定了一個cell的類毅厚,也是無法向其內(nèi)部拖入IBOutlet的。這就是靜態(tài)cell的局限性浦箱,但是如果你要設(shè)置的數(shù)據(jù)不多還是可以考慮用靜態(tài)cell吸耿,因為你可以通過給cell上的控件設(shè)tag來找到它從而賦值。
動態(tài)cell的使用
在上圖中設(shè)置content屬性為Dynamic Prototypes就可以了酷窥。使用動態(tài)cell的話就要在VC中實現(xiàn)那兩個必須實現(xiàn)的datasource協(xié)議了咽安,下面舉個簡單的例子:
1.給cell指定一個class
2.給cell設(shè)置id,重用機(jī)制使用蓬推,其他屬性就不詳細(xì)說明了妆棒,和其他控件的屬性差不多。
添加tableHeaderView和tableFooterView
是的沸伏,沒有聽錯糕珊,不用代碼,“拖”出header于footer毅糟,其實很簡單红选,選中tablView,在控件中找到View拖到tableView上姆另,往最上方拖動喇肋,知道看到左右有兩個圈的時候松手坟乾,這個View就是tableHeaderView了,同理蝶防,往最下方拖糊渊,就是tableFooterView。
LaunchScreen.storyboard
從iOS8開始iPhone多了4.7"和5.5"的兩種設(shè)備慧脱,這使得適配更加復(fù)雜渺绒,特別是設(shè)置啟動圖,如果考慮到橫豎屏的話菱鸥,要做好多張圖宗兼,最重要的是,啟動圖是最占體積的東西氮采,為了更好殷绍、更方面的配置啟動圖,LaunchScreen.storyboard出現(xiàn)了鹊漠,簡單來說主到,啟動的時候會加載這個SB文件,我們可以同過它更方便的設(shè)置啟動圖躯概,可以用auto layout減少啟動圖數(shù)量的使用登钥,但此功能只支持iOS8及其以上的系統(tǒng)。
那么問題來了娶靡,我要想適配更低的系統(tǒng)怎么辦牧牢?答:不用。
如何禁止該功能姿锭?
Storyboard Entry Point
如果我們用xcode6或者更高版本的xcode創(chuàng)建工程的話,你會發(fā)現(xiàn)自動就有了一個Main.storyboard
application:didFinishLaunchingWithOptions:中沒有一行代碼運行就沒有問題呻此,并不像之前那樣轮纫,要創(chuàng)建window,指定rootViewController焚鲜,這些是如何實現(xiàn)的掌唾?
xcode自動配置了一個SB文件,而以上的這一切都有xcode自動幫我們完成了恃泪。
那么問題來了:一個SB是可以對應(yīng)多個VC的郑兴,他選哪個VC作為window的rootViewController犀斋?答案是Storyboard Entry Point贝乎,這個東西就是用來指定那個作為rootViewController的,也就是說叽粹,xcode會找到表示為Storyboard Entry Point的那個VC加載它成為rootViewController览效,而以后的跳轉(zhuǎn)就由我們之前介紹的方式:
勾選就是設(shè)置了Storyboard Entry Point却舀,設(shè)置了Storyboard Entry Point的VC會有一個向右的箭頭指向它,注意你在Main Interface里選的SB文件中一定要有VC勾選了這個锤灿,不然xcode是不知道如何設(shè)置rootViewController的挽拔,你不用擔(dān)心多選的問題,你如果選擇一個新的VC但校,舊的那個VC就自然沒有了Storyboard Entry Point螃诅,但是如果你又取消了勾選那么舊的VC并不會自動又添加Storyboard Entry Point的,要小心状囱。
高冷用法
IBAction與IBOutlet
這是我們最常接觸的兩個术裸,大家對它們已經(jīng)有了很好的認(rèn)識,這里只簡單的說一下亭枷。
對于一個類來說袭艺,方法和屬性(在這里屬性與字段合在一起表示一個概念)是最重要的兩個要素,而IBAction與IBOutlet就是分別標(biāo)識方法與屬性的叨粘,它們標(biāo)識著由它們修飾的方法和屬性是來自xib的猾编,我猜它們是給編譯器看的。
IBInspectable
在OC中使用IBInspectable升敲,在swift中使用@IBInspectable
它是xcode6引入的新功能答倡,它修飾的屬性或者實例變量,會顯示在xib中的屬性欄中(Show the Attributes inspector)驴党,我們之前講的東西都是xib是如何影響代碼的苇羡,而IBInspectable是可以用代碼影響xib的叼屠,可能我的表述不是很正確于微,還是看一個具體例子吧。
@interface ViewController : UIViewController
//gj_testFlag用IBInspectable修飾后似袁,就能在xib中看到這個屬性了攘轩,當(dāng)然也可以用xib進(jìn)行賦值了
@property (assign, nonatomic) IBInspectable BOOL gj_testFlag;
@end
```![1490498-0a46dcda9e2d9982.jpg](http://upload-images.jianshu.io/upload_images/2318672-eca4b84372b52bf8.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
###IB_DESIGNABLE
在OC中將IB_DESIGNABLE寫在@implementation前叉存,在swift中將@IBDesignable寫在class前
它也是xcode6引入的新功能,它的作用是可以在不運行的情況下把你的代碼顯示在xib或SB文件中歼捏。
兩點說明:
1.這是一個針對UI顯示的功能瞳秽,所以只能是在UIView及其子類或者NSView及其子類上生效练俐。
2.要想使IBDesignable起作用必須把代碼寫在drawRect里才能顯示腺晾,同樣的代碼悯蝉,我寫在了awakeFromNib里就不會再xib中看出效果鼻由,只有寫在了drawRect才可以蕉世。
舉個例子:
我們建一個工程讨彼,新建一個TestView類繼承自UIVIew哈误,在Main.storyboard里拖一個View蜜自,class設(shè)置為TestView重荠,背景設(shè)置成灰色戈鲁。
![1490498-e7e06b420aa167ad.jpg](http://upload-images.jianshu.io/upload_images/2318672-8003435c42d37fe8.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
IB_DESIGNABLE
@implementation TestView
- (void)drawRect:(CGRect)rect {
UIBezierPath *firtPath =
[UIBezierPath bezierPathWithOvalInRect:CGRectMake(10, 10, 180, 180)];
CAShapeLayer *shapeL = [CAShapeLayer layer];
shapeL.lineWidth = 20;
shapeL.path =firtPath.CGPath;
shapeL.strokeStart = 0;
shapeL.strokeEnd = 1;
shapeL.strokeColor = [UIColor yellowColor].CGColor;
shapeL.fillColor = [UIColor clearColor].CGColor;
[self.layer addSublayer:shapeL];
self.layer.cornerRadius = 30;
self.layer.masksToBounds = YES;
}
@end
![1490498-bb8d92cace1eaaab.jpg](http://upload-images.jianshu.io/upload_images/2318672-a03e52aaefce99ac.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
###IBOutletCollection(ClassName):
將基于IBOutlet創(chuàng)建的對象放在一個NSarray里。
@property (strong, nonatomic) IBOutletCollection(UIView) NSArray *testViewArr;
創(chuàng)建了一個array怕磨,里面放的是用IBOutlet創(chuàng)建的UIView.
注意最好用strong進(jìn)行修飾肠鲫,而且如果你聲明的不是NSArray导饲,即便是UIColor,系統(tǒng)也不會報錯泡挺,你打印這個color發(fā)現(xiàn)娄猫,系統(tǒng)用的還是NSArray媳溺。這個array的順序是連線時候的順序悬蔽,但是不排除不同版本的xcode會改變這個順序蝎困,所以最好不要依賴這個順序。
也可以像拖IBOutlet那樣創(chuàng)建:
###Files Owner的應(yīng)用舉例
有這樣一個場景始藕,VC中有一個textfield要設(shè)置inputAccessoryView屬性伍派,該屬性的view顯示起來很復(fù)雜拙已,有多個按鈕倍踪,每個按鈕對應(yīng)不同的事件。
一般的做法是用代碼寫一個這樣的view賦值給inputAccessoryView屬性缤至,其實這個例子可以用xib實現(xiàn)的更優(yōu)雅领斥,不用寫代碼就可以完成(當(dāng)然點擊每個按鈕后的事件處理代碼是要自己寫的)何恶。
例子中要考慮的重點是:
如果創(chuàng)建了一個AccessoryView.xib去拖出這樣一個view细层,雖然不用“畫”UI了,但是我們要建一個AccessoryView.h捧搞、AccessoryView.m類去與xib文件對應(yīng)实牡,在AccessoryView.m中把它上面的按鈕事件記錄下來,一旦觸發(fā)事件题涨,要通過delegate或通知等其他形式把事件從AccessoryView類傳遞給VC類纲堵,這樣使事情更加的麻煩了,如何解決茂附?
有人會想:創(chuàng)建AccessoryView.h、AccessoryView.m是沒有必要的蒂阱,因為他們除了傳遞事件鳄厌,根本沒做任何事情了嚎,這樣的話就不創(chuàng)建他們窖梁,只有AccessoryView.xib文件邀窃,然后把xib中的按鈕分別拖動到VC類中建立起IBAction的“連線”關(guān)系,事情就搞定了肪虎。
這個思路很好,但是我們會發(fā)現(xiàn)迅腔,并不能實現(xiàn)AccessoryView.xib與VC中的“連線”,因為VC類根本不認(rèn)識這個xib,因此該VC是不允許這個xib通過“連線”向它內(nèi)部添加代碼的,如何解決這個問題汤锨?——Files Owner!
將AccessoryView.xib的Files Owner指定成該VC的類慎菲,此時再拖“連線”到VC就可以了,這樣xib中按鈕的事件就能直接回調(diào)到VC中我們設(shè)置的方法里了解幼。
![1490498-39be8c63b59f1de8.jpg](http://upload-images.jianshu.io/upload_images/2318672-7f59af2081958963.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
xib文件是可以不依托于UIView子類害晦、UIViewController子類單獨使用的鲫剿,只是這種情況比較少見,這是一個例子。