聲明:本文絕非原創(chuàng),筆者只是站在巨人的肩膀上總結(jié)網(wǎng)絡(luò)上各位大神的筆記和博客文章,在此向大神們致敬!
在iOS開發(fā)過程中,頁面跳轉(zhuǎn)時在頁面之間進(jìn)行數(shù)據(jù)傳遞是很常見的事情,我們稱這個過程為頁面?zhèn)髦?/strong>。頁面跳轉(zhuǎn)過程中,從一級頁面跳轉(zhuǎn)到二級頁面的數(shù)據(jù)傳遞稱之為正向傳值;反之,從二級頁面返回一級頁面時的數(shù)據(jù)傳遞稱之為反向傳值。
準(zhǔn)備工作
為了實現(xiàn)頁面之間傳值,我們需要準(zhǔn)備兩個頁面,代碼結(jié)構(gòu)如下圖所示。其中,MainViewController為一級頁面,SubViewController為二級頁面,頁面之間的跳轉(zhuǎn)使用UINavigationController來實現(xiàn)。
頁面之間的跳轉(zhuǎn)動畫如下所示,每個頁面中都有一個文本編輯框,我們需要將其中一個頁面文本框中的內(nèi)容傳遞到另一個頁面中。
1. 屬性傳值
方法描述:在從當(dāng)前頁面跳轉(zhuǎn)到下一級頁面之前,提前創(chuàng)建下一級頁面,通過賦值的方式將當(dāng)前頁面的數(shù)據(jù)賦予下一級頁面的屬性。傳遞方式:正向傳值。
適用場景:當(dāng)從一級頁面push到二級頁面時,二級頁面需要使用到一級頁面的數(shù)據(jù),我們需要使用到正向傳值。
例如,我們首先需要在二級頁面中定義一個*NSString text屬性,用于保存一級頁面?zhèn)鬟f給下一級頁面的數(shù)據(jù)。在SubViewController.h中定義屬性代碼如下:
@interface SubViewController : UIViewController
@property (copy,nonatomic) NSString *text;
@property (weak,nonatomic) IBOutlet UITextField *textField;
- (IBAction)backJumpBtnClicked:(id)sender;
@end
然后,需要在一級頁面按鈕按下時的跳轉(zhuǎn)代碼中將當(dāng)前頁面的textField中的內(nèi)容通過屬性賦值的方式傳遞給二級頁面中的text屬性。
- (IBAction)mainJumpBtnClicked:(id)sender {
SubViewController *subPage = [[SubViewController alloc]init];
subPage.text = _textField.text;
[self.navigationController pushViewController:subPage animated:YES];
}
然后,需要在SubViewController的viewDidLoad中設(shè)置頁面中textField的內(nèi)容:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
_textField.text = _text;
}
注意:如果你使用如下的方式進(jìn)行屬性傳值并不能成功。
SubViewController *subPage = [[SubViewController alloc]init];
subPage.textField.text = _textField.text;
[self.navigationController pushViewController:subPage animated:YES];
目前,我也不知道出現(xiàn)這種情況的真正原因,不過分析可能是由于二級頁面中的textField是從xib文件到導(dǎo)出的,數(shù)據(jù)weak類型,可能這個時候還沒有分配內(nèi)存的關(guān)系導(dǎo)致了賦值失敗。
2. 代理傳值
方法描述:首先在二級頁面的頭文件中添加一個代理(協(xié)議)的定義,定義一個傳遞數(shù)據(jù)的方法,并且在二級頁面的類中添加一個代理屬性;然后,在二級頁面返回上一級頁面之前調(diào)用代理中定義的數(shù)據(jù)傳遞方法(方法參數(shù)就是要傳遞的數(shù)據(jù));最后,在一級頁面中遵從該代理,并實現(xiàn)代理中定義的方法,在方法的實現(xiàn)代碼中將參數(shù)傳遞給一級頁面的屬性。
使用場景:已經(jīng)通過push的方式進(jìn)入到二級頁面,在從二級頁面返回一級頁面的時候(二級頁面會釋放掉內(nèi)存),需要在一級頁面中使用二級頁面中的數(shù)據(jù),這是就可以利用代理反向傳值。
第1步:在二級頁面的頭文件中添加一個代理的定義。
第2步:在二級頁面的屬性中添加一個代理屬性。二級頁面的頭文件如下:
#import <UIKit/UIKit.h>
// 聲明代理
@protocol SubToMainDelegate <NSObject>
// 代理方法
- (void)transferData:(NSString*)text;
@end
@interface SubViewController : UIViewController
@property (weak,nonatomic) IBOutlet UITextField *textField;
- (IBAction)backJumpBtnClicked:(id)sender;
// 代理屬性
@property (weak,nonatomic) id<SubToMainDelegate> delegate;
@end
第3步:在二級頁面消失之前,調(diào)用數(shù)據(jù)傳遞的代理方法,通過該方法將二級頁面中的數(shù)據(jù)傳遞給實現(xiàn)了該代理方法的對象。二級頁面的實現(xiàn)文件代碼如下:
#import "SubViewController.h"
@interface SubViewController ()
@end
@implementation SubViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)backJumpBtnClicked:(id)sender {
// 判斷有沒有代理以及代理是否響應(yīng)代理方法
if (self.delegate &&
[self.delegate respondsToSelector:@selector(transferData:)]) {
[self.delegate transferData:self.textField.text];
}
[self.navigationController popViewControllerAnimated:YES];
}
@end
第4步:在一級頁面中遵從二級頁面中定義的協(xié)議。
第5步:實現(xiàn)代理中的數(shù)據(jù)傳遞方法。一級頁面的實現(xiàn)代碼如下:
#import "MainViewController.h"
#import "SubViewController.h"
@interface MainViewController ()<SubToMainDelegate>
@end
@implementation MainViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)mainJumpBtnClicked:(id)sender {
SubViewController *subPage = [[SubViewController alloc]init];
subPage.delegate = self;
[self.navigationController pushViewController:subPage animated:YES];
}
// MARK: 實現(xiàn)SubToMainDelegate中的代理方法
- (void)transferData:(NSString *)text{
self.textField.text = text;
}
@end
注意: 從動畫可以看出,我們通過點擊二級頁面的返回上一級按鈕時,可以實現(xiàn)反向傳值。但是,如果直接點擊導(dǎo)航欄的Back鍵,并不能實現(xiàn)傳值效果,這是因為導(dǎo)航欄的返回按鈕沒有實現(xiàn)代理中的方法,解決辦法是添加一個自定義按鈕代替導(dǎo)航欄左側(cè)的返回按鈕,并且將新添加的按鈕selector設(shè)置為返回上一級按鈕的方法。
3. Block傳值
方法描述:在二級頁面中添加一個塊語句屬性,在二級頁面返回一級頁面之前調(diào)用該塊語句。在一級頁面跳轉(zhuǎn)二級頁面之前,設(shè)置二級頁面中的塊語句屬性將要執(zhí)行的動作(回調(diào)函數(shù))。這樣,在二級頁面返回一級頁面時就會調(diào)用該回調(diào)函數(shù)來傳遞數(shù)據(jù)。
適用場景:反向傳值。
第1步:在二級頁面的頭文件中定義一個Block屬性,代碼如下:
#import <UIKit/UIKit.h>
// 定義一個Block
typedef void(^SubToMainBlock)(NSString *text);
@interface SubViewController : UIViewController
@property (weak,nonatomic) IBOutlet UITextField *textField;
- (IBAction)backJumpBtnClicked:(id)sender;
// 添加一個Block屬性
@property (copy,nonatomic) SubToMainBlock data;
@end
第2步:在點擊返回上一級按鈕的事件處理代碼中調(diào)用塊語句傳值,二級頁面的實現(xiàn)文件代碼內(nèi)容如下:
#import "SubViewController.h"
@interface SubViewController ()
@end
@implementation SubViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)backJumpBtnClicked:(id)sender {
// Block傳值
_data(self.textField.text);
[self.navigationController popViewControllerAnimated:YES];
}
@end
第3步:在一級頁面跳轉(zhuǎn)二級頁面之前,設(shè)置在二級頁面執(zhí)行的塊語句回調(diào)函數(shù),代碼如下:
#import "MainViewController.h"
#import "SubViewController.h"
@interface MainViewController ()
@end
@implementation MainViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)mainJumpBtnClicked:(id)sender {
SubViewController *subPage = [[SubViewController alloc]init];
__weak typeof(self) mainPtr = self;
// Block回調(diào)接收數(shù)據(jù)
[subPage setData:^(NSString *text){
mainPtr.textField.text = text;
}];
[self.navigationController pushViewController:subPage animated:YES];
}
@end
注: 程序的運行效果與方法2代理傳值的運行效果相同。
4. KVO傳值
方法描述:KVO(Key-Value-Observing,鍵值觀察),即觀察關(guān)鍵字的值的變化。首先在二級頁面中聲明一個待觀察的屬性,在返回一級頁面之前修改該屬性的值。在一級頁面中提前分配并初始化二級頁面,并且注冊對二級頁面中對應(yīng)屬性的觀察者。在從二級頁面返回上一級之前,通過修改觀察者屬性的值,在一級頁面中就能自動檢測到這個改變,從而讀取二級頁面的數(shù)據(jù)。
適用場景:反向傳值。
KVO使用三大步:
(1) 注冊觀察者
(2) KVO的回調(diào)
(3) 移除觀察者
以上三大步都在一級頁面中實現(xiàn),代碼如下:
#import "MainViewController.h"
#import "SubViewController.h"
@interface MainViewController ()
@property (strong,nonatomic) SubViewController *subPage;
@end
@implementation MainViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)mainJumpBtnClicked:(id)sender {
// 懶加載
if (_subPage == nil){
_subPage = [[SubViewController alloc]init];
// 注冊觀察者
[_subPage addObserver:self forKeyPath:@"data" options:NSKeyValueObservingOptionNew context:nil];
}
[self.navigationController pushViewController:_subPage animated:YES];
}
// KVO的回調(diào)
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
if ([keyPath isEqualToString:@"data"]){
self.textField.text = _subPage.data;
}
}
// 移除KVO
- (void)dealloc{
[_subPage removeObserver:self forKeyPath:@"data"];
}
注冊觀察者中的forKeyPath參數(shù)用于指定需要觀察的二級頁面中的屬性名稱。
說明:代碼中對于二級頁面使用了懶加載的方式,創(chuàng)建了該頁面之后,返回一級頁面時此頁面并不會釋放內(nèi)存,因此,下一次進(jìn)入二級頁面時數(shù)據(jù)保持不變。
首先,在二級頁面中生命我們需要在一級頁面中觀察的屬性“data”:
#import <UIKit/UIKit.h>
@interface SubViewController : UIViewController
@property (weak,nonatomic) IBOutlet UITextField *textField;
- (IBAction)backJumpBtnClicked:(id)sender;
// 添加一個觀察的屬性
@property (copy,nonatomic) NSString *data;
@end
然后,在二級頁面返回上一級之前,修改在一級頁面中觀察的屬性的值:
#import "SubViewController.h"
@interface SubViewController ()
@end
@implementation SubViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)backJumpBtnClicked:(id)sender {
// 修改屬性的值,在一級頁面中監(jiān)聽該屬性
self.data = self.textField.text;
[self.navigationController popViewControllerAnimated:YES];
}
@end
小心陷阱:在修改觀察的屬性時,不能使用簡寫_data = self.textField.text;雖然這種寫法在我們平時的使用中可以與self.data等效,但是在KVO中如果使用_data來修改data屬性的值,一級頁面并不能檢測到這種改變。因此,必須使用完整的self.data形式來修改data屬性的值。
注意:觀察者的注冊和移除要對應(yīng),如果移除時發(fā)現(xiàn)沒有注冊觀察者,程序會crash。
5. 通知傳值
方法描述:
適用場景:正向傳值、反向傳值。
6. 單例傳值
方法描述:
適用場景:正向傳值、反向傳值。
7. KVC傳值
方法描述:
適用場景:正向傳值。
總結(jié):本文介紹了iOS中頁面?zhèn)髦档某S梅椒?,歸納主要分為屬性傳值、代理傳值、Block傳值、KVO傳值、通知傳值、單例傳值和KVC傳值這7種方法。文中以示例的方式介紹了每種方法的用法和使用場景,以便開發(fā)者在產(chǎn)品開發(fā)過程中選擇和參考。