引言
下面我們來學習比xib更強大的SB
如何理解SB?
最簡單的理解就是:一個.storyboard文件相當于多個基于VC使用的.xib文件,由此我們可以看出,SB(只能基于VC,不能基于View),我們有多個VC的類是可以和一個SB文件建立關系的,使用方法:
SecVC *secVC = [[UIStoryboard storyboardWithName:@"Demo" bundle:[NSBundle mainBundle]] instantiateViewControllerWithIdentifier:@"SecVC"];
之前的一天一點xib:2初識xib有過介紹, 通過這個方法,我們傳入不同VC的identifier,就會得到不同的VC對象。
為什么是強大了?
xib可以基于View、VC甚至自己獨立的使用,而SB只能基于VC使用,為什么說比xib更加強大呢?主要是下面的兩個原因:
1.SB支持segue
2.SB對cell的支持更加強大
什么是segue?
想象我們點擊一個VC中的button后跳轉到另一個VC的場景,通常用的都是navigationController的push,或者present一個模態視圖,即使有xib的情況下,要完成跳轉其實也不簡單,要把按鈕拖到@implementation中實現一個IBAction的方法,然后在里面寫上:
[self.navigationController pushViewController:Sec animated:YES]
或者
[self presentViewController:Sec animated:YES completion:nil]
要想簡化這個沒有技術含量的過程,就要用到SB的segue。
我們在一個VC中選擇要發生跳轉的按鈕,按control拖動到另一個VC上就會出現一個菜單,在菜單上你就可以選擇跳轉的方式push、present,這樣不用寫一行代碼就能完成頁面間的跳轉,而兩個VC之間的像紐扣一樣用線連著兩個VC的的東西就是segue,是一個UIStoryboardSegue對象,我們可以簡單的理解成是完成頁面跳轉相關功能的一個類,是不是很簡單?
segue雖然簡單,如何傳參?
我們知道用SB的segue來實現也頁面跳轉十分方便,但是如果要向跳轉的頁面傳參該怎么辦?
假設我們要點擊ViewController這個VC里的一個按鈕,跳轉到SecVC這個VC中,把testTitle這個參數傳過去。第一步還是要選中按鈕,拖到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";
}
}
設置SB中segue的identifier,主要就是要在這里使用,因為多個button發生的多個頁面跳轉都是回調這個函數,所以要用identifier來區分是哪個segue,跳哪個頁面,傳哪些參數。
id的sender就是觸發跳轉的那個button或其他控件。
不確定的跳轉如何使用segue?
一下場景經常出現:甲VC中有個button,點擊后有可能跳轉到乙頁面,也可能跳轉的丙頁面,怎么解?
我們假設我們要點擊ViewController這個VC里的一個按鈕,有可能要跳到SecVC,有可能要跳thirdVC。
1.在SB中準備segue。
注意我們這次創建的segue的方式并不是選中button按control拖到另一個VC中,而是選中VC,右鍵選中triggered segues里manual后面的+拖動到另一個VC里,這個很重要,而且還是要給segue一個id。
2.編碼實現跳轉:
- (IBAction)testSegue:(id)sender
{
BOOL flag = NO;
if (flag) {
[self performSegueWithIdentifier:@"sec" sender:sender];
} else {
[self performSegueWithIdentifier:@"third" sender:sender];
}
}
代碼實現segue跳轉也很簡單:
- (void)performSegueWithIdentifier:(NSString *)identifier sender:(nullable id)sender
第一個參數是segue的id,第二個參數是觸發這個跳轉的控件。
在segue跳轉頁面的過程中還會調一個函數,我們可以重寫這個函數,來決定是否讓這個跳轉發生。eg:
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender
{
if (_islogin) {
return YES;
} else {
NSLog(@"您還沒有登錄,請先登錄"); //用log舉個例子
return NO;
}
}
更靈活的segue
如果你熟練掌握了segue他其實可以做很多事情,其實segue并不僅僅能完成跳轉,還有一類segue叫做relationship segue,之前是幫助大家理解,所以說的比較簡單,其實我們選中一個NavigationController按control鍵拖動到另一個VC上的話也可以生成segue,我們選中root view controller,就將Nav的rootViewController屬性設置成了這個VC,這個segue就是relationship segue,同樣,拖動一個tabbarController也會出現viewControllers的relationship segue。
更好的使用Cell
用xib的時候,要想用cell,通常是建立一個cell子類,一個與之對應的.xib文件。用SB的時候要想用cell就簡單的多了,因為cell是可以直接拖動到tableView里的,直接在tableView里管理cell設置屬性,當然如果是復雜的cell還是要子類話對應的.h、.m的。
SB在使用cell分兩種情況:1靜態cell,2動態cell。
靜態cell
設置靜態cell:
注意靜態cell一定要基于UITableViewController,否則會報錯。
展示靜態cell的tableView是不用調用自己必須實現的datasource協議的
你會很驚訝,為什么必須調用的datasource協議都不用實現?答案是靜態,靜態的cell,意義就在于你在xib中設置成什么樣,他就展現什么樣,不會再調用datasource向你要cell了,因為在SB文件中已經確定下來了。
在做一些“死”頁面的時候SB的靜態cell是很好的選擇,靜態cell也不是什么都不能做,靜態cell里的button還是可以拖到@implementation中形成IBAction的,但是是無法生成IBOutlet屬性或字段的。
即使你強行的給一個靜態的cell指定了一個cell的類,也是無法向其內部拖入IBOutlet的。這就是靜態cell的局限性,但是如果你要設置的數據不多還是可以考慮用靜態cell,因為你可以通過給cell上的控件設tag來找到它從而賦值。
動態cell的使用
在上圖中設置content屬性為Dynamic Prototypes就可以了。使用動態cell的話就要在VC中實現那兩個必須實現的datasource協議了,下面舉個簡單的例子:
1.給cell指定一個class
2.給cell設置id,重用機制使用,其他屬性就不詳細說明了,和其他控件的屬性差不多。
3.給cell添加一些控件,并拖到TestCell源文件中生成IBOutlet的屬性,在VC中實現那兩個必須實現的協議方法:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 20;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TestCell *testCell = (TestCell *)[tableView dequeueReusableCellWithIdentifier:@"testCell"];
testCell.testLabel.text = @"hello world";
return testCell;
}
這里注意,不需要在tableView:cellForRowAtIndexPath:中創建cell了,這是與xib使用cell最大的區別,下面是xib使用cell的方法舉例:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TestCell *testCell = (TestCell *)[tableView dequeueReusableCellWithIdentifier:@"testCell"];
if (!testCell) {
testCell = [[NSBundle mainBundle] loadNibNamed:@"TestCell" owner:self options:nil];
}
testCell.testLabel.text = @"hello world";
return testCell;
}
這是很重要的,千萬記住,因為我們是直接拖cell到SB文件的tableView里的,所以直接從重用池里取就好了。
這里稍微提一個地方:我們這里取重用cell用的是函數:
dequeueReusableCellWithIdentifier:,而系統中還有另一個重用的函數:dequeueReusableCellWithIdentifier:forIndexPath:這個函數比我們代碼中用的那個好,是我們用的那個api的優化版,它是iOS6引入的api,完全符合我們一般app對版本兼容的要求,這里感謝簡書用戶coderzcj在評論中給我留言,謝謝你。我后續會再寫一篇文章專門說說系統對xib優化的事情的,非常感謝大家的支持。
總結
SB的segue和cell使用是它最大意義的地方,當然之前xib能做的設置,SB也都能做,而且多個.xib對應一個SB文件,使文件量變少了不少,但是如果想單獨用View的地方還是要用xib。
歡迎大家和我交流溝通,若文章中有錯誤和紕漏,懇請指正,謝謝。