延續前一篇的文章使用 Storyboard Segue 實作 UIViewController 的切換(上),我們已經成功建立兩個可以互相切換的 UIViewController,一個是透過 Storyboard Segue 來切換,另一個則是使用 dismissViewControllerAnimated: 的方法來返回先前的 UIViewController,接下來就是要解決兩個 UIViewController 之間傳值的問題,這裡同樣提供兩個方法,在頁面 1 的部份同樣使用 Storyboard Segue 來幫助我們傳遞資訊給頁面 2,而在頁面 2 的部份,則是使用老方法,透過代理委托 delegate 的方式來傳送資訊。
首先我們在兩個 UIViewController 分別拉一個 UITextField 元件,並將此元件與 class 做連結,分別命名為 page1TextField 與 page2TextField,連結的方式可以參考從使用 UIButton 說 Hello 開始說起(上)ㄧ文,如果是使用 Xcode 4 的朋友也可以直接參考 Xcode 4 的 Assistant Editor 關聯編輯功能ㄧ文,節省撰寫程式碼的時間。
在透過 Storyboard Segue 傳值的部份,我們必須在頁面 2 的 UIViewController class 裡設置一個 NSString 的變數,它的目的是用來接收由頁面 1 透過 Storyboard Segue 所傳過來的資訊,程式碼如下。
@property (weak) NSString *string;
//別忘了在對應的實作檔中加入@synthesize string;
之後在 viewDidLoad 的函式裡我們將 string 的值指定給 page2TextField,這個動作會讓頁面 2 的畫面在被開啓時就會把 page2TextField 的內容設成 Storyboard Segue 所傳送過來的值。
- (void)viewDidLoad
{
[super viewDidLoad];
page2TextField.text = string;
}
接著回到頁面 1 的 UIViewController class 裡,新增一個內建的函式如下,就完成透過 Storyboard Segue 傳值的方法。
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
//將page2設定成Storyboard Segue的目標UIViewController
id page2 = segue.destinationViewController;
//將值透過Storyboard Segue帶給頁面2的string變數
[page2 setValue:page1TextField.text forKey:@"string"];
}
另一個方法就是使用代理委托 delegate 的方法,透過建立一個協定 @protocol 的方式,讓其它的採納此協定的 class 可以實作協定內的函式,我們在頁面 2 的 UIViewController class 裡設置一個協定,並且在頁面 1 的 UIViewController class 裡實作協定裡的方法,讓程式執行到頁面 2 時,仍然能夠取得頁面 1 的實例 Instance,進而使用協定裡的方法來設定 page2TextField 的數值。
首先來到頁面 2 的 UIViewController class,建立一個協定 Page2Delegate,並且定義其內部的方法 passValue:,接著宣告一個採用此協定的物件 delegate,其程式碼如下。
#import <UIKit/UIKit.h>
//建立一個協定
@protocol Page2Delegate
//協定中的方法
- (void)passValue:(NSString *)value;
@end
@interface Page2ViewController : UIViewController
@property (weak, nonatomic) IBOutlet UITextField *page2TextField;
@property (weak) NSString *string;
//宣告一個採用Page2Delegate協定的物件
@property (weak) id<Page2Delegate> delegate;
@end
接下來就是決定協定中的方法要在什麼時候生效,也就是要在什麼地方呼叫 passValue: 方法函式,而此函式會在採用此協定的類別 Class 中被實作。回顧程式的需求,我們希望在頁面 2 按下返回頁面 1 的按鈕時,能將 page2TextField 的數值傳給頁面 1 的 page1TextField,所以呼叫協定 passValue: 方法的程式碼,勢必要寫在此按鈕事件中。
- (IBAction)returnToFirstPage:(id)sender {
[self dismissViewControllerAnimated:YES completion:^{}];
//呼叫協定中的方法並帶入page2textField的數值
[delegate passValue:page2TextField.text];
}
現在,當我們按下頁面 2 的按鈕時,就會呼叫採用 Page2Delegate 協定的 class,而此 class 必須要實作此協定內的方法,所以回到頁面 1 的 UIViewController class,我們要替此 class 採用 Page2Delegate 的協定,並且實作協定內的 passValue: 方法函式。
採用協定的方式是在 @interface 區段的地方加上 <協定名稱> 的程式碼,由於此協定是寫在別的 class 中,所以在採用協定之前別忘了先引用它,以下是頁面 1 的 UIViewController class .h 標頭檔。
#import <Uikit/Uikit.h>
//引用持有Page2Delegate協定的class
#import "Page2ViewController.h"
@interface MLViewController : UIViewController <page2delegate> //採用協定
@property (weak, nonatomic) IBOutlet UITextField *page1TextField;
@end
接著在 .m 實作檔中實作協定內的 passValue: 方法函式。
- (void)passValue:(NSString *)value {
//設定page1TextField為所取的的數值
page1TextField.text = value;
}
最後一個步驟,也就是大家常常會忘記的,要將代理 delegate 設成自己(頁面 1 的 UIViewController),也就是大家在使用協定時最常寫的一行程式碼 object. delegate = self(object 指的就是頁面 2 的 UIViewController),至於這行程式碼要寫在哪裡?還記得之前透過 Storyboard Segue 傳值的部份,我們已經藉由內建的函式取得頁面 2 的 UIViewController 實例 page2,所以我們修改此內建函式,利用 page2 來設定 delegate 變數。
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
//將page2設定成Storyboard Segue的目標UIViewController
id page2 = segue.destinationViewController;
//將值透過Storyboard Segue帶給頁面2的string變數
[page2 setValue:page1TextField.text forKey:@"string"];
//將delegate設成自己(指定自己為代理)
[page2 setValue:self forKey:@"delegate"];
}
如果忽略此步驟,頁面 2 裡的 delegate 參數在呼叫 passValue 方法時,並不會知道是誰(哪個 class)實作了它的方法,因此參數也無法由頁面 2 傳遞至頁面 1。
ps:如果你對於解散 UITextField 的小鍵盤有問題,可以參考UITextField 輸入結束後的收起小鍵盤的方式ㄧ文。
您好,我有个问题是關於第一種傳值方法的。
回覆刪除如果我1個view要連到2個view的話,該傳值方法是不是就要求所連接的2個view中都必鬚聲明同樣的string變量了呢?
你是說同時傳到兩個不同的 ViewController 中嗎?
回覆刪除如果是按照上面的範例 segue 應該只能一對一才是一個 ViewController 對另一個 ViewController,想要指向兩個不同的 ViewController 可能需要在新增一個觸發的方式(新增一個新的按鈕)
最後,基於上述的原因不太可能按下「同一個按鈕」就觸發兩個不同的 segue 並且呈現兩個不同的 ViewController ,但是 segue 所提供的內建方法函式只有一個,在我們沒有辦法分辨彼此的情況下,就需要「宣告同樣的 string 變數」,我想這可能就是您想想問的問題,在 ViewController 接到不同的變數之後才做不同的處理,
這樣的觀念類似於 Super Class 與 Sub Class 之間的關係,他們可能都擁有同樣的變數名稱或是方法函式,但應用的方式卻不太一樣。
簡單來說你有兩個 ViewController,即使他們彼此用到的資訊是完全不同的,但是它們仍必須將對方所需要用到的變數做宣告,只是在接到數值之後您可以選擇不使用它。
同樣地,來源端的 ViewController 在透過 segue 的方法函式傳遞數值時,也必須傳送所有的資訊出去,因為他不知道哪一個 ViewController 會接到這些訊息。
希望這樣解釋能幫助你解決問題!
非常感謝!
刪除我是再1個ViewController中有2個按鈕,每個按鈕打開一個不同的ViewController。
使用第一種方法時,運行報錯,提示我没有找到string,(只有一個ViewController包含了string變數)
現在明白了,使用這種方法的話,需要宣告對方所包含的變數,即便這個變數不會被使用到。
謝謝
嗯嗯,很高興有幫到你。
刪除非常棒的教學,
回覆刪除在這個網站獲益良多,謝謝你們的努力!
您好:
刪除謝謝您的支持~!!
很實用的教學,推~
回覆刪除不過我想請教一個問題
若是第2個viewcontroller換成UINavigationView
且此UINavigationView後面帶有一個UIViewController
那該如何把第1個UIViewController的textfield值傳入呢?
匿名者您好:
刪除依照您的意思是想設計成
「主要ViewContorller」 --按下按鈕-->「NavigationController」---自動帶出root view-->「次要ViewContorller」。
如果是按照上述設計,您必須在storyboard上也做好對應的連結:
-主要ViewContorller 上的按鈕透過Segue(model)連結到 NavigationController
-NavigationController 設定次要ViewContorller為root view controller
-此時次要ViewContorller畫面邵方會自動多出一條NavigationBar
之後在傳值的關鍵程式碼處做以下修改:
UINavigationController *page2 = segue.destinationViewController;
NSArray *viewControllers = page2.viewControllers;
UIViewController *rootViewController = [viewControllers objectAtIndex:viewControllers.count - 1];
[rootViewController setValue:page1TextField.text forKey:@"string"];
原理是使用一個NSArray來取得UINavigationController下的所有viewControllers,只後再判斷誰是rootViewController
之後delegate的設定方式也如法炮製。
感謝牛奶~
刪除目前已經能把第1個ViewController的資料帶到UINavigationView的UIViewController了~
可是卻不知道怎麼實作Delegate回到第1個ViewController
主要是卡在第1個ViewController的PrepareForSegue中
該如何設定delegate呢?
匿名者您好:
刪除delegate 的設定方式,就與上述一樣,您可以自行研究看看,建議妳可以實作使用 Storyboard Segue 實作 UIViewController 的切換(上)(下)
兩篇文章,最後再自行插入 UINavigationController,唯一要變動的部份就只有 「主要ViewController」的程式碼,因為「次要ViewController」的程式碼,主要是回傳值給 Delegate,當然協定的用意本身就是不知道在執行時最後會由誰來執行。
你可以試試看
[rootViewController setValue:self forKey:@"delegate"];
也許會有幫助。
可以了~
刪除是自己想的太複雜啦 XD
謝謝牛奶,獲益良多^^
匿名者您好:
刪除不用客氣喔!
您好~~超好的觀念練習~很受用~~想再請問一下~我自己試著 用 2各 tableviewcontroller 內cell 間 資料轉移 可是一直跑不出來~~想問的是在tableviewcontroller 使用segue 是不是有需注意的地方~
回覆刪除Bryan Chen 您好:
回覆刪除關於2個 UITableViewController 的 CELL 資料轉移,您是透過使用 segue 的方式來製作,如果只是簡單的由A傳到B這方面是不會有問題,但是如果是AB互享傳資料,可能最出現不是唯一instance的疑慮,這點在 使用 Storyboard Segue 實作 UIViewController 的切換(下)這一篇文章中有說明。
由於在使用 Storyboard Segue 來傳送資料時是單向的,當我們建立好 A CONTROLLER 之後,透過 Storyboard Segue 傳參數給 B CONTROLLER,才隨即建立取得 B CONTROLLER 實體,反之經 B CONTROLLER 透過 Storyboard Segue 傳送過來的參數,雖然是傳給 A CONTROLLER,但是他卻不會是我們原先的 A CONTROLLER,你會得到以下這樣的結果。
A --> B -create-> A(new) 而不是 A --> B -return-> A
另外,也許您的觀念上沒錯,只是我在字面上的誤解,兩個 UITableViewController 中,並不存在 CELL 的資料轉移,由上面的範例我們可以知道 UIViewController(UITableViewController)只能透過 Storyboard Segue 將數值傳給另一個 UIViewController(UITableViewController),CELL 上的數值呈現只是在 UIViewController(UITableViewController)被建立時,他自己去做設定才會生效,無法由其他人 UIViewController 來控制。
最後你可以參考看看 在兩個不同的 Class 之間傳遞參數的方法 一文,也許會對您有些幫助。
http://furnacedigital.blogspot.tw/2012/07/class.html
真的太感謝的你的教學文了!!!!
回覆刪除您好:
刪除不客氣喔!
請問如果是第一個View上有一個button 按下後,第二個view 為tabbar,想要按下按鈕後傳值給tabbar的view ,用storyboard 應該怎麼做?
回覆刪除你好:
刪除新年快樂,我不太確定你的問題是不是這個,你可以參考看看:
http://stackoverflow.com/questions/18120549/passing-the-value-from-one-view-to-other-view-in-uitabbarcontroller
另外在兩個不同的class之間傳遞參數,不限於使用協定,你可以參考這篇文章:
http://furnacedigital.blogspot.tw/2012/07/class.html
謝謝您的回覆,
刪除我想要做的是第一個view1為登入畫面可輸入帳號,按按鈕後會進入view2 & view 3(為tabbar切換畫面)
view1輸入的帳號,view2&view 3可以收到,
根據您提供的範例,可以成功利用appDelegate傳值,謝謝您,給予新手的我很大的幫助
但同例想用segue傳值,都一直傳不成功,
請問如果用segue要如何寫?是否有例子可參考,謝謝您
您好:
刪除我建議妳可以使用tabbar樣板來測試,在tabbar樣板中的storyboard裡也同樣有「segue」,我自己是沒試過,不過應該也可透過增加 (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender 函式的方式來傳送參數,確認都ok沒問題,你可以在自行新增一個uiviewcontroller,把起始畫面指向他(移動storyboard中uiviewcontroller的箭頭即可),來實作帳號密碼輸入的介面。
謝謝,我會再試試看
刪除請問 有沒有方法可以在prepareForSegue之前先動作
回覆刪除比如我要加入loading畫面
我是有試過利用按鈕touch up inside先觸發一個+入LOADING畫面的動作
但是程式看起來當我按下的時候
已經在prepareForSegue
然後畫面就卡住在第二頁 VIEWDIDLOAD的部分了
您好
刪除還是建議你將load的整個歷程做在第二頁,畢竟整個預載的資源都要在第二頁做計算
如果硬要做在第一頁的話,你必須把切換頁面的程式碼動作寫在一個單獨的函式中
當你按下換頁按鈕會觸發你預載畫面的等待函式,整個結束後,才去呼叫剛剛那個切頁的單獨函式
你好
刪除我的目的主要是要在切換的時候加入loading畫面
因為我第二頁的有些資訊是從網路上抓的
所以切換的時候都會等待一段時間
我是想在他第2頁viewdidload之前的這段時間加入loading畫面
這樣看起來比較不會以為是當機
假如說在第2頁viewdidload才加入loading畫面
當資源載入完成之後 好像也不必要loading畫面
是否有辦法達成這個目的
您好:
刪除我說的方式就是再換頁面前做讀取畫面,上面說的方式是寫在第一頁唷,不是第二頁。
如果你要將讀取畫面實作再第二頁,你要把loading動畫放在viewdidload的地方,直到你網頁資訊都ready後,才去停止他。
兩個方法都是一樣意思,你必須要能夠抓到你下載網路資源完成的那個瞬間!
你好!我想傳兩個欄位,設兩個(void)passValue,前面沒什問題,但是卡在最後這裡
回覆刪除- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
id page2 = segue.destinationViewController;
[page2 setValue:nameTextField.text forKey:@"string"];
id page2 = segue.destinationViewController;
[page2 setValue:ageTextField.text forKey:@"string1"];
[page2 setValue:self forKey:@"delegate"];
} 我的問題是setValue這裡,好像只能傳一個值,小弟卡在這邊,牛奶大可否指點一下迷津,謝謝!
您好:
刪除確認一下你第一個變數 string 有接收到正確的數值嗎?然後你另一個變數 string1 在 page2 也確定有這個變數!
如果這樣還是不行的話,可以建議你傳「物件」過去,而不是單一字串。
試試看把兩個字串變成一個物件一次傳過去!
謝謝大大!問題已解決。
刪除大大,小弟再請教一個問題,當第二頁的值已經傳到第一頁了,並且顯示在第一頁,但是我想把傳過去的值,隱藏起來,TextField顯示空白。該怎麼做?我用self.ageTexField.hidden 會把文字包含整個欄位隱藏,改用.self.ageTexField.text hidden 沒辦法跑,哪裡需要改一下,謝謝您!
刪除你忘記還可以用 text color (文字顏色)了
刪除文字顏色=背景顏色 也可當做隱藏
阿哈!我倒是沒想到,謝謝牛奶大!。我還想釐清一個小問題。先把view1用segue連到view2。之後用Storyboard Segue將值由view1傳到view2,這部分行得通。但是要將view2的值回傳到view1,為何非得用到delegate?為何不用Storyboard Segue回傳?小弟這點不解,望牛奶大指點迷津。
刪除這個有點兒深奧,這牽扯到兩個VIEW的instance(實例)!
刪除Storyboard Segue 這是屬於View1的東西,View2並不會知道。
在這個例子中,只有View1認識View2,並且在建立View2的同時設定其上的delegate指向自己,這樣可以上View2的函式轉移到View1上被實作,藉由函式的轉移,帶入的參數也可以帶至View1來執行了。
當然想要在兩個CLASS之間傳遞參數,不只是使用delegate而已,你可以參考這邊的文章實作看看!!
http://furnacedigital.blogspot.tw/2012/07/class.html