這篇我們完成Storyboards的最后一個例子,之前的例子中沒有view之間的切換,這篇加上這個功能,使Storyboards的功能完整呈現。在Storyboards中負責view切換的東西叫做“segue”,只需對它進行簡單的設置即可,一切都是傻瓜式的,無需繁瑣的代碼。好了,開始我們的例子吧。
1)Create a Simple Storyboard
創建一個project,左邊選擇Application,右邊選擇Empty Application template(我們這里不使用Single View Application,而是創建了一個Empty Application,之后我們會自己手動添加storyboard,這樣你就可以更好的了解storyboard使用方法了),點擊Next
將項目命名為Seg Nav,點擊Next,完成項目創建
由於我們要創建的項目是基於storyboard,因此需要對BIDAppDelegate.m中didFinishLaunchingWithOptions方法進行如下修改
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; // Override point for customization after application launch. self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible];
return YES;
}
刪除所有行,只保留最后的return YES。
2)添加storyboard
鼠標右擊project navigator中的Seg Nav文件夾,然后選擇New File...,在彈出的對話框中,左邊選擇User Interface,右邊選擇Storyboard,點擊Next
之后的Device Family選擇iPhone,點擊Next,將storyboard命名為MainStoryboard.storyboard,完成創建
之后一步我們所要做的是將MainStoryboard.storyboard設置為程序啟動是默認載入的對象,選中project navigator中最頂端的項目名稱,這樣summary tab就會出現,找到里面的Main Storyboard選項,將其值設置為我們剛剛添加的MainStoryboard,這樣在陳旭啟動后會默認的載入這個storyboard。
3)設置MainStoryboard.storyboard
再次選中MainStoryboard.storyboard,這是它里面什么都沒有,layout area中也是空空如也,在object library中找到Navigation Controller,然后將其拖入layout area中,這樣在layout area中瞬間就多了2個controller
為什么會是2個controller而不是一個呢?這是因為UINavigationController只包含一個navigation bar,沒有其他的東西,因此當我們拖一個Navigation Controller進入layout area后,系統自動為這個Navigation Controller關聯了一個Table View Controller,這樣就完整了。上圖中,2個Controller的中間有一個箭頭相連,它表示右邊的Table View Controller是左邊Navigation Controller的rootViewController(記住箭頭中間的圓圈,之后關聯其他Controller的時候,其他箭頭中間的形狀會有所不同)
在dock中選中Table View Controller的Table View
打開attrbutes inspector,將Content設置為“Static Cells”,layout area中的table view上出現了3個cell,我們只需要2個就夠了,刪除其中的一個
在dock中依次選中2個Table View Cell,然后在attributes inspector中將他們的Style改成“Basic”
這樣在cell上會出現Title
將上面的一個Title改成“Single view”,下面的一個Title改成“Sub-menu”(直接雙擊Title進行修改)
然后我們對Table View Controller上的Navigator bar進行操作,在dock中選中Navigation item
將attributes inspector中Title設值為“Segue Navigator”,將Back Button設值為“Seg Nav”
注意,這里的Back Button並不是顯示在當前的navigator bar上的,而是顯示在下一個sub view controller的navigator bar上的返回按鈕的文字,用於表面將返回到哪個父contoller
編譯運行一下程序
我們可以看到MainStoryboard.storyboard作為默認的view被載入到程序中,界面上顯示了我們修改后的navigator bar,還有table view中的2個cell。這樣子是不是很簡單?到目前為止,對Storyboard的操作完全是基於Interface Builder的,我們沒有寫過任何代碼。
4)創建第一個Segue例子
在project navigator中選中MainStoryboard.storyboard,然后從Object library中找到View Controller,將其拖入到layou area中,放置在現有controller的右邊
然后在Object library中找到Label,拖入到剛才添加的View Controller中,將Label的文字改成“Single view”,從添加的文字可以知道,這個view是通過點擊table view中的第一個cell打開的
下面就是通過Segue將table view中的第一個cell和Single view關聯起來,首先選中table view中的第一個cell,然后control-drag到新添加的view,然后釋放鼠標,這時會有一個彈出框彈出,
這個彈出框有2部分組成,Selection Segue和Accessory Action,這2部分的選項是相同的。
Selection Segue的意思是當用戶點擊table view cell的任何部分,都會產生反應。
Accessory Action的意思是只有當用戶點擊table view cell右邊的圓圈箭頭按鈕時,才會產生的反應。
在這里,我們選擇Selection Segue的push選項(大家可以去一個一個選擇其他的選項,看看有什么不同),選中后,在view的上方會自動出現一個navigator bar的占位欄,而在table view cell的右邊會出現一個大於號箭頭,它們2個view直接會有一個箭頭相連
ok,現在編譯運行一下,看看效果,點擊table view中的第一個cell,切換到下面的view,可以看到,view的最上方是navigator bar,bar的左邊是一個退回的按鈕,按鈕中顯示的文字是“Seg Nav”,還記得剛才我們在設置Navigation Item時,在其attributes inspector中的Back Button項中留下的文字嗎?就是顯示在這里的。
點擊Seg Nav按鈕可以回到上級view。這個操作過程是不是很簡單?回憶之前幾篇的例子,我們這里沒有寫過一行代碼,而得到的效果是一樣的,Storyboard在這方面還是很強大的,它讓程序員的全部注意力都集中在了具體的view的開發上,view之間的切換它都幫我們搞定了。
4)創建第二個Segue例子
下面我們為table view中的第二個cell創建一個controller,然后通過segue將他們連接起來。
第二個cell將連接到我們上一篇的例子Simple Storyboard中BIDTaskListController(點擊下載),因此我們在這里可以稍微偷懶一下,直接將他們拖入到現在的項目中
接着我們還需要添加一個文件,選中Project navigator中的Seg Nav文件夾,單擊鼠標右鍵,選擇“New File...”,在彈出的窗口中,左邊選擇Cocoa Touch,右邊選擇Objective-C class,點擊Next按鈕,在下一個窗口中將class命名為BIDTaskDetailController,Subclass of命名為UIViewController,點擊Next按鈕,完成創建。
接着選中MainStoryboard.storyboard,從Object library中拖一個Table View Controller到layout area(你可以適當調整view的位置,使其布局美觀合理)
選中新添加的Table View Controller,打開identity inspector,將Class設置為BIDTaskListController
猜到我們接下來要做什么了嗎?由於BIDTaskListController是從之前的Simple Storyboard復制過來的,因此我們需要和之前一樣,在新添加的Table View Controller中放2個cell,設置每個cell的identifier,為每個cell中添加1個Label,然后分別為他們設置tag值,並將第二個Label的顏色設置為紅色。
首先在dock中選中第一個cell(現在應該只有唯一一個cell存在),然后打開attributes inspector,將其identifier賦值為“plainCell”,往第一個cell中拖一個Label,左右拉伸Label直至輔助線的位置,選中Label,打開attributes inspector,找到Tag,設值為1。
再選中第一個cell,然后Command+D,復制一個cell,新的cell出現在其下方,選中新的cell,在attributes inspector中將其identifier賦值為“attentionCell”,再選中Label,在attributes inspector將Label的顏色改成紅色,設置Tag值仍為1。
在開始連接segue之前,我們還需要添加另外一個view controller,用於顯示並修改TaskList中每一項的內容,並和BIDTaskDetailController關聯,從Object library中拖一個View Controller到layout area,放在Task List Controller的右邊,然后在identity inspector中將其class設置為BIDTaskDetailController
當我們在Task List Controller中選中一個cell后,view會切換到Task Detail Controller,並在里面顯示cell的文字,我們在Task Detail Controller中可以對cell的文字進行修改保存,然后再返回Task List Controller。因此我們首先需要在Task Detail Controller中添加一個UITextView,UITextView應該是我們第一次接觸到,它是一個多行的文本視圖,和C#中的TextBox類似。
在Object library中找到Text View
拖入到Task Detail Controller中,這時Text View會自動填充滿整個view的空間
我們並不需要它占滿整個空間,因此我們去調成它的高度,使其只占滿view的上半部分,因為下半部分會顯示虛擬鍵盤,調整Text View的高度到200(移動上圖中底下中間的那個小白方框進行調整,你也可以在Size inspector中進行調整,看個人喜歡了),寬度還是占滿整個view的
好了,終於開始代碼的部分了,在dock中選中Task Detail Controller
然后點擊Assistant editor,這時應該會打開對應的BIDTaskDetailController.h
選中Text View,然后control-drag到BIDTaskDetailController.h,在填出框中,name命名為textView,點擊Connect。
好了,下面可以開始連接Segue。選中Table View Controller中的第二個cell,然后control-drag到Task List Controller,在填出框中選擇Selection Segue的push選項。然后選擇Task List Controller中的第一個cell,control-drag到Task Detail Controller,在填出框中選擇Selection Segue的push選項,選擇Task List Controller中的第二個cell,control-drag到Task Detail Controller,在填出框中選擇Selection Segue的push選項,這樣就有2個箭頭同時從Task List Controller指向Task Detail Controller,但是他們的意義是不一樣的。
整個的MainStoryboard.storyboard的結構如下圖所示
編譯運行一下程序,所以controller之間都應該可以順利切換了。
但是我在運行的時候卻發現了一個問題,當我點擊Sub-menu准備切換到Task List Controller時,發現Task List Controller竟然是空白的一片,而起xcode也報錯說:Unknown class BIDTaskListController in Interface Builder file
我打開BIDTaskListController.h,發現UITableViewController的文字顏色是黑色,這就說明xcode沒有認出整個類,那就說明肯定是編譯器出來問題,沒有設置正確
網上查了一圈后發現這種現象是xcode的一個bug,因為BIDTaskListController不是我們從xcode創建的,而是從另外一個項目中拖進來的,因此xcode沒有把它包含進編譯范疇,因此也就沒有去識別里面所包含的類。解決整個問題的方法也很簡單,我們選中Project navigator中的Seg Nav項目,然后打開Build Phases,展開Compile Sources,點擊+號,把BIDTaskListController.m添加進去就可以了。
在此編譯運行,你會發現controller都正確顯示了,xcode中的錯誤也消失了。
下面我們實現一些方法,使每個controller能夠正確的工作,打開BIDTaskListController.m,添加如下代碼
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { UIViewController *destination = segue.destinationViewController; if ([destination respondsToSelector:@selector(setDelegate:)]) { [destination setValue:self forKey:@"delegate"]; } if ([destination respondsToSelector:@selector(setSelection:)]) { // prepare selection info NSIndexPath *indexPath = [self.tableView indexPathForCell:sender]; id object = self.tasks[indexPath.row]; NSDictionary *selection = @{@"indexPath" : indexPath, @"object" : object}; [destination setValue:selection forKey:@"selection"]; } }
prepareForSegue:sender方法,當tasklist中的任何cell被點擊,並准備開始進行view直接的切換時,這個方法會被觸發,這樣我們可以利用這個方法來傳輸一些信息給下一個view使用。
UIViewController *destination = segue.destinationViewController; // 通過參數segue,我們可以知道接下來即將顯示的controller是哪個,segue還有另外一個參數segue.sourceViewController,這個是表示即將被移除的controller是哪個
if ([destination respondsToSelector:@selector(setDelegate:)]) { // respondsToSelector用來判斷是否實現了某些方法,這里是判斷在目標controller中是否實現了setDelegate方法
[destination setValue:self forKey:@"delegate"]; } // 這里是KVC(KEY-VALUE CODING)的一個用法,將目標對象中key為delegate的對象賦值為self,當然到目前為止,在BIDTaskDetailController中什么方法都沒有實現,是空的
if ([destination respondsToSelector:@selector(setSelection:)]) { //用來判斷在目標對象中是否存在setSelection方法
// prepare selection info
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender]; // 這里的sender指向的是用戶點擊的那個cell,通過它來獲得cell的indexPath
id object = self.tasks[indexPath.row]; // 獲取NSArray中的對象
NSDictionary *selection = @{@"indexPath" : indexPath, @"object" : object}; // 創建一個NSDictionary對象,將indexPath和object對象,然后將他們通過KVC的方法傳到目標controller中,這里傳遞indexPath的作用是如果在BIDTaskDetailController中把object的內容給改了,那么傳回來的時候,我們就知道改的是哪個cell了,否則們會雲里霧里,不知道哪個cell的內容需要更新
[destination setValue:selection forKey:@"selection"]; } // 這里又用到KVC,將目標對象selection的值設置為selection
下面開始修改BIDTaskDetailController.h,打開它,添加如下代碼
#import <UIKit/UIKit.h> @interface BIDTaskDetailController : UIViewController @property (weak, nonatomic) IBOutlet UITextView *textView; @property (copy, nonatomic) NSDictionary *selection; @property (weak, nonatomic) id delegate; @end
剛才使用KVC賦值的2個對象在這里定義好了,注意,selection用的是copy
打開BIDTaskDetailController.m,添加如下代碼
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.textView.text = self.selection[@"object"]; [self.textView becomeFirstResponder]; }
在viewDidLoad方法中,首先對textView進行賦值,selection在之前的controller中使用KVC進行過賦值,在這里直接獲取就可以了,然后調用becomeFirstResponder,呼出虛擬鍵盤。
ok,再編譯運行一下你的程序,看看效果,隨便在BIDTaskListController中選擇一個cell,然后立刻會跳轉到BIDTaskDetailController,而且textView中會顯示cell的內容,並且出現虛擬鍵盤
但是如果現在修改了textView的內容,然后返回,textView中的內容是沒有辦法保存的,我們還沒有實現這個功能,那是不是我們也可以用剛才同樣的方法prepareForSegue來傳遞值呢?很不幸,不可以,因為prepareForSegue只有當將一個controller放到堆棧上面的時候可以使用,如果將一個controller從堆棧上面移除,是無法使用的,這個具有單向性,好吧,那我們只有另尋他法了,在BIDTaskDetailController.m中添加如下代碼
- (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; if ([self.delegate respondsToSelector:@selector(setEditedSelection:)]) { // finish editing [self.textView endEditing:YES]; // prepare selection info NSIndexPath *indexPath = self.selection[@"indexPath"]; id object = self.textView.text; NSDictionary *editedSelection = @{@"indexPath" : indexPath, @"object" : object}; [self.delegate setValue:editedSelection forKey:@"editedSelection"]; } }
現在再看這個方法,應該是很熟悉了吧,里面實現的東西還是一樣的,只是沒有放在prepareForSegue中而已,setEditedSelection等一會會在BIDTaskListController中實現,需要解釋的貌似就一個[self.textView endEditing:YES]:停止一切對textView的編輯動作,等對textView的操作都結束后,那么就可以獲取它的值,然后返回了。其他的代碼都應該可以理解,最后也用到了KVC方法。
最后還是需要對BIDTaskListController進行一些修改,打開BIDTaskListController.m,做如下修改
@interface BIDTaskListController () @property (strong, nonatomic) NSArray *tasks; @property (strong, nonatomic) NSMutableArray *tasks; @property (copy, nonatomic) NSDictionary *editedSelection; @end
我們將tasks對象替換成可修改的Array,然后再聲明一個NSDictionary對象editedSelection
- (void)viewDidLoad { [super viewDidLoad]; // Uncomment the following line to preserve selection between presentations. // self.clearsSelectionOnViewWillAppear = NO; // Uncomment the following line to display an Edit button in the navigation bar for this view controller. // self.navigationItem.rightBarButtonItem = self.editButtonItem; self.tasks = @[@"Walk the dog", @"URGENT: Buy milk", @"Clean hidden lair", @"Invent miniature dolphins", @"Find new henchmen", @"Get revenge on do-gooder heroes", @"URGENT: Fold laundry", @"Hold entire world hostage", @"Manicure"]; self.tasks = [@[@"Walk the dog", @"URGENT: Buy milk", @"Clean hidden lair", @"Invent miniature dolphins", @"Find new henchmen", @"Get revenge on do-gooder heroes", @"URGENT: Fold laundry", @"Hold entire world hostage", @"Manicure"] mutableCopy]; }
將數組copy到tasks對象中
- (void)setEditedSelection:(NSDictionary *)dict { if (![dict isEqual:self.editedSelection]) { _editedSelection = dict; NSIndexPath *indexPath = dict[@"indexPath"]; id newValue = dict[@"object"]; [self.tasks replaceObjectAtIndex:indexPath.row withObject:newValue]; [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; } }
if (![dict isEqual:self.editedSelection]) { // 首先判斷dictionary對象是否發生了變化,如果是,繼續執行代碼
_editedSelection = dict; // _editedSelection也是一個objective-c的語法現象,它是隱式的被創建的,在聲明
@property (strong, nonatomic) NSMutableArray *tasks;的時候,系統自動聲明的了一個對象_editedSelection,你可以發現,這里並沒有之前的synthesize方法,這里系統已經幫我們做完了(貌似以后的編程越來越方便了)
NSIndexPath *indexPath = dict[@"indexPath"]; // 獲取indexPath對象
id newValue = dict[@"object"]; // 獲取object對象
[self.tasks replaceObjectAtIndex:indexPath.row withObject:newValue]; // 根據indexPath替換tasks中的對象
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; } // 重新載入更新過的cell
好了,至此,左右的代碼都寫完了,我們的這個例子也完成了,編譯運行,試着修改一些cell的內容,然后返回,看看是不是變了
好了,所有關於Storyboard的內容都講完了,是不是覺得還是蠻簡單的,是不是覺得以后再遇到Navigation的項目,都會用Storyboard,而不會自己去寫繁瑣的code進行view直接的切換呢,好吧,如果沒有十分的必要,書本上也建議使用Storyboard的,這樣我們可以將更多的精力放在功能的實現上,而無需去處理繁瑣的切換效果。