天天看點

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

從零開始學ios開發(十八):Storyboards(下)

這篇我們完成Storyboards的最後一個例子,之前的例子中沒有view之間的切換,這篇加上這個功能,使Storyboards的功能完整呈現。在Storyboards中負責view切換的東西叫做“segue”,隻需對它進行簡單的設定即可,一切都是傻瓜式的,無需繁瑣的代碼。好了,開始我們的例子吧。

1)Create a Simple Storyboard 建立一個project,左邊選擇Application,右邊選擇Empty Application template(我們這裡不使用Single View Application,而是建立了一個Empty Application,之後我們會自己手動添加storyboard,這樣你就可以更好的了解storyboard使用方法了),點選Next

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

将項目命名為Seg Nav,點選Next,完成項目建立

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

由于我們要建立的項目是基于storyboard,是以需要對BIDAppDelegate.m中didFinishLaunchingWithOptions方法進行如下修改

從零開始學ios開發(十八):Storyboards(下) - 小 鬼
- (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;}      
從零開始學ios開發(十八):Storyboards(下) - 小 鬼

删除所有行,隻保留最後的return YES。

2)添加storyboard 滑鼠右擊project navigator中的Seg Nav檔案夾,然後選擇New File...,在彈出的對話框中,左邊選擇User Interface,右邊選擇Storyboard,點選Next

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

之後的Device Family選擇iPhone,點選Next,将storyboard命名為MainStoryboard.storyboard,完成建立

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

之後一步我們所要做的是将MainStoryboard.storyboard設定為程式啟動是預設載入的對象,選中project navigator中最頂端的項目名稱

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

,這樣summary tab就會出現,找到裡面的Main Storyboard選項,将其值設定為我們剛剛添加的MainStoryboard,這樣在陳旭啟動後會預設的載入這個storyboard。

3)設定MainStoryboard.storyboard 再次選中MainStoryboard.storyboard,這是它裡面什麼都沒有,layout area中也是空空如也,在object library中找到Navigation Controller

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

,然後将其拖入layout area中,這樣在layout area中瞬間就多了2個controller

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

為什麼會是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

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

打開attrbutes inspector,将Content設定為“Static Cells”,layout area中的table view上出現了3個cell,我們隻需要2個就夠了,删除其中的一個

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

在dock中依次選中2個Table View Cell,然後在attributes inspector中将他們的Style改成“Basic”

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

這樣在cell上會出現Title

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

将上面的一個Title改成“Single view”,下面的一個Title改成“Sub-menu”(直接輕按兩下Title進行修改)

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

然後我們對Table View Controller上的Navigator bar進行操作,在dock中選中Navigation item

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

将attributes inspector中Title設值為“Segue Navigator”,将Back Button設值為“Seg Nav”

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

注意,這裡的Back Button并不是顯示在目前的navigator bar上的,而是顯示在下一個sub view controller的navigator bar上的傳回按鈕的文字,用于表面将傳回到哪個父contoller

編譯運作一下程式

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

我們可以看到MainStoryboard.storyboard作為預設的view被載入到程式中,界面上顯示了我們修改後的navigator bar,還有table view中的2個cell。這樣子是不是很簡單?到目前為止,對Storyboard的操作完全是基于Interface Builder的,我們沒有寫過任何代碼。

4)建立第一個Segue例子 在project navigator中選中MainStoryboard.storyboard,然後從Object library中找到View Controller

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

,将其拖入到layou area中,放置在現有controller的右邊

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

然後在Object library中找到Label,拖入到剛才添加的View Controller中,将Label的文字改成“Single view”,從添加的文字可以知道,這個view是通過點選table view中的第一個cell打開的

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

下面就是通過Segue将table view中的第一個cell和Single view關聯起來,首先選中table view中的第一個cell,然後control-drag到新添加的view,然後釋放滑鼠,這時會有一個彈出框彈出,

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

這個彈出框有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直接會有一個箭頭相連

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

ok,現在編譯運作一下,看看效果,點選table view中的第一個cell,切換到下面的view,可以看到,view的最上方是navigator bar,bar的左邊是一個退回的按鈕,按鈕中顯示的文字是“Seg Nav”,還記得剛才我們在設定Navigation Item時,在其attributes inspector中的Back Button項中留下的文字嗎?就是顯示在這裡的。

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

點選Seg Nav按鈕可以回到上級view。這個操作過程是不是很簡單?回憶之前幾篇的例子,我們這裡沒有寫過一行代碼,而得到的效果是一樣的,Storyboard在這方面還是很強大的,它讓程式員的全部注意力都集中在了具體的view的開發上,view之間的切換它都幫我們搞定了。

4)建立第二個Segue例子 下面我們為table view中的第二個cell建立一個controller,然後通過segue将他們連接配接起來。

第二個cell将連接配接到我們上一篇的例子Simple Storyboard中BIDTaskListController(點選下載下傳),是以我們在這裡可以稍微偷懶一下,直接将他們拖入到現在的項目中

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

接着我們還需要添加一個檔案,選中Project navigator中的Seg Nav檔案夾,單擊滑鼠右鍵,選擇“New File...”,在彈出的視窗中,左邊選擇Cocoa Touch,右邊選擇Objective-C class,點選Next按鈕,在下一個視窗中将class命名為BIDTaskDetailController,Subclass of命名為UIViewController,點選Next按鈕,完成建立。

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

接着選中MainStoryboard.storyboard,從Object library中拖一個Table View Controller到layout area(你可以适當調整view的位置,使其布局美觀合理)

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

選中新添加的Table View Controller,打開identity inspector,将Class設定為BIDTaskListController

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

猜到我們接下來要做什麼了嗎?由于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。

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

在開始連接配接segue之前,我們還需要添加另外一個view controller,用于顯示并修改TaskList中每一項的内容,并和BIDTaskDetailController關聯,從Object library中拖一個View Controller到layout area,放在Task List Controller的右邊,然後在identity inspector中将其class設定為BIDTaskDetailController

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

當我們在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

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

拖入到Task Detail Controller中,這時Text View會自動填充滿整個view的空間

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

我們并不需要它占滿整個空間,是以我們去調成它的高度,使其隻占滿view的上半部分,因為下半部分會顯示虛拟鍵盤,調整Text View的高度到200(移動上圖中底下中間的那個小白方框進行調整,你也可以在Size inspector中進行調整,看個人喜歡了),寬度還是占滿整個view的

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

好了,終于開始代碼的部分了,在dock中選中Task Detail Controller

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

然後點選Assistant editor

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

,這時應該會打開對應的BIDTaskDetailController.h

選中Text View,然後control-drag到BIDTaskDetailController.h,在填出框中,name命名為textView,點選Connect。

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

好了,下面可以開始連接配接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的結構如下圖所示

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

編譯運作一下程式,是以controller之間都應該可以順利切換了。

但是我在運作的時候卻發現了一個問題,當我點選Sub-menu準備切換到Task List Controller時,發現Task List Controller竟然是空白的一片,而起xcode也報錯說:Unknown class BIDTaskListController in Interface Builder file

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

我打開BIDTaskListController.h,發現UITableViewController的文字顔色是黑色,這就說明xcode沒有認出整個類,那就說明肯定是編譯器出來問題,沒有設定正确

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

網上查了一圈後發現這種現象是xcode的一個bug,因為BIDTaskListController不是我們從xcode建立的,而是從另外一個項目中拖進來的,是以xcode沒有把它包含進編譯範疇,是以也就沒有去識别裡面所包含的類。解決整個問題的方法也很簡單,我們選中Project navigator中的Seg Nav項目,然後打開Build Phases,展開Compile Sources,點選+号,把BIDTaskListController.m添加進去就可以了。

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

在此編譯運作,你會發現controller都正确顯示了,xcode中的錯誤也消失了。

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

下面我們實作一些方法,使每個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的内容,并且出現虛拟鍵盤

從零開始學ios開發(十八):Storyboards(下) - 小 鬼

但是如果現在修改了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的内容,然後傳回,看看是不是變了

從零開始學ios開發(十八):Storyboards(下) - 小 鬼
從零開始學ios開發(十八):Storyboards(下) - 小 鬼

好了,所有關于Storyboard的内容都講完了,是不是覺得還是蠻簡單的,是不是覺得以後再遇到Navigation的項目,都會用Storyboard,而不會自己去寫繁瑣的code進行view直接的切換呢,好吧,如果沒有十分的必要,書本上也建議使用Storyboard的,這樣我們可以将更多的精力放在功能的實作上,而無需去處理繁瑣的切換效果。

Seg Nav