iOS中页面传值(正向传值与反向传值)总结

声明:本文绝非原创,笔者只是站在巨人的肩膀上总结网络上各位大神的笔记和博客文章,在此向大神们致敬!

在iOS开发过程中,页面跳转时在页面之间进行数据传递是很常见的事情,我们称这个过程为页面传值。页面跳转过程中,从一级页面跳转到二级页面的数据传递称之为正向传值;反之,从二级页面返回一级页面时的数据传递称之为反向传值


准备工作

为了实现页面之间传值,我们需要准备两个页面,代码结构如下图所示。其中,MainViewController为一级页面,SubViewController为二级页面,页面之间的跳转使用UINavigationController来实现。

代码结构

页面之间的跳转动画如下所示,每个页面中都有一个文本编辑框,我们需要将其中一个页面文本框中的内容传递到另一个页面中。

页面之间的跳转

1. 属性传值

方法描述:在从当前页面跳转到下一级页面之前,提前创建下一级页面,通过赋值的方式将当前页面的数据赋予下一级页面的属性。传递方式:正向传值。

适用场景:当从一级页面push到二级页面时,二级页面需要使用到一级页面的数据,我们需要使用到正向传值

例如,我们首先需要在二级页面中定义一个*NSString text属性,用于保存一级页面传递给下一级页面的数据。在SubViewController.h中定义属性代码如下:

@interface SubViewController : UIViewController
@property (copy,nonatomic) NSString *text;
@property (weak,nonatomic) IBOutlet UITextField *textField;
- (IBAction)backJumpBtnClicked:(id)sender;
@end

然后,需要在一级页面按钮按下时的跳转代码中将当前页面的textField中的内容通过属性赋值的方式传递给二级页面中的text属性。

- (IBAction)mainJumpBtnClicked:(id)sender {
    SubViewController *subPage = [[SubViewController alloc]init];
    subPage.text = _textField.text;
    [self.navigationController pushViewController:subPage animated:YES];
}

然后,需要在SubViewController的viewDidLoad中设置页面中textField的内容:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
    _textField.text = _text;
}
属性传值运行效果

注意:如果你使用如下的方式进行属性传值并不能成功

SubViewController *subPage = [[SubViewController alloc]init];
subPage.textField.text = _textField.text;
[self.navigationController pushViewController:subPage animated:YES];

目前,我也不知道出现这种情况的真正原因,不过分析可能是由于二级页面中的textField是从xib文件到导出的,数据weak类型,可能这个时候还没有分配内存的关系导致了赋值失败。

2. 代理传值

方法描述:首先在二级页面的头文件中添加一个代理(协议)的定义,定义一个传递数据的方法,并且在二级页面的类中添加一个代理属性;然后,在二级页面返回上一级页面之前调用代理中定义的数据传递方法(方法参数就是要传递的数据);最后,在一级页面中遵从该代理,并实现代理中定义的方法,在方法的实现代码中将参数传递给一级页面的属性。

使用场景:已经通过push的方式进入到二级页面,在从二级页面返回一级页面的时候(二级页面会释放掉内存),需要在一级页面中使用二级页面中的数据,这是就可以利用代理反向传值

第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步:在二级页面消失之前,调用数据传递的代理方法,通过该方法将二级页面中的数据传递给实现了该代理方法的对象。二级页面的实现文件代码如下:

#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 {
    // 判断有没有代理以及代理是否响应代理方法
    if (self.delegate &&
       [self.delegate respondsToSelector:@selector(transferData:)]) {
        [self.delegate transferData:self.textField.text];
    }
    [self.navigationController popViewControllerAnimated:YES];
}
@end

第4步:在一级页面中遵从二级页面中定义的协议。
第5步:实现代理中的数据传递方法。一级页面的实现代码如下:

#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: 实现SubToMainDelegate中的代理方法
- (void)transferData:(NSString *)text{
    self.textField.text = text;
}
@end

代理传值.gif

注意: 从动画可以看出,我们通过点击二级页面的返回上一级按钮时,可以实现反向传值。但是,如果直接点击导航栏的Back键,并不能实现传值效果,这是因为导航栏的返回按钮没有实现代理中的方法,解决办法是添加一个自定义按钮代替导航栏左侧的返回按钮,并且将新添加的按钮selector设置为返回上一级按钮的方法。

3. Block传值

方法描述:在二级页面中添加一个块语句属性,在二级页面返回一级页面之前调用该块语句。在一级页面跳转二级页面之前,设置二级页面中的块语句属性将要执行的动作(回调函数)。这样,在二级页面返回一级页面时就会调用该回调函数来传递数据。

适用场景:反向传值

第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步:在点击返回上一级按钮的事件处理代码中调用块语句传值,二级页面的实现文件代码内容如下:

#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步:在一级页面跳转二级页面之前,设置在二级页面执行的块语句回调函数,代码如下:

#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回调接收数据
    [subPage setData:^(NSString *text){
        mainPtr.textField.text = text;
    }];
    [self.navigationController pushViewController:subPage animated:YES];
}
@end

: 程序的运行效果与方法2代理传值的运行效果相同。

4. KVO传值

方法描述:KVO(Key-Value-Observing,键值观察),即观察关键字的值的变化。首先在二级页面中声明一个待观察的属性,在返回一级页面之前修改该属性的值。在一级页面中提前分配并初始化二级页面,并且注册对二级页面中对应属性的观察者。在从二级页面返回上一级之前,通过修改观察者属性的值,在一级页面中就能自动检测到这个改变,从而读取二级页面的数据。

适用场景:反向传值

KVO使用三大步
(1) 注册观察者
(2) KVO的回调
(3) 移除观察者
以上三大步都在一级页面中实现,代码如下:

#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的回调
- (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参数用于指定需要观察的二级页面中的属性名称。
说明:代码中对于二级页面使用了懒加载的方式,创建了该页面之后,返回一级页面时此页面并不会释放内存,因此,下一次进入二级页面时数据保持不变。

KVO传值.gif

首先,在二级页面中生命我们需要在一级页面中观察的属性“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 {
    // 修改属性的值,在一级页面中监听该属性
    self.data = self.textField.text;
    [self.navigationController popViewControllerAnimated:YES];
}
@end

小心陷阱:在修改观察的属性时,不能使用简写_data = self.textField.text;虽然这种写法在我们平时的使用中可以与self.data等效,但是在KVO中如果使用_data来修改data属性的值,一级页面并不能检测到这种改变。因此,必须使用完整的self.data形式来修改data属性的值。

注意:观察者的注册和移除要对应,如果移除时发现没有注册观察者,程序会crash。

5. 通知传值

方法描述:

适用场景:正向传值反向传值

6. 单例传值

方法描述:

适用场景:正向传值反向传值

7. KVC传值

方法描述:

适用场景:正向传值


总结:本文介绍了iOS中页面传值的常用方法,归纳主要分为属性传值、代理传值、Block传值、KVO传值、通知传值、单例传值和KVC传值这7种方法。文中以示例的方式介绍了每种方法的用法和使用场景,以便开发者在产品开发过程中选择和参考。

参考资料

[1] Jingege,iOS页面传值知多少?你真的了解吗?简书

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,637评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,598评论 18 399
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,134评论 30 470
  • 最近,在外面徒步,有一种明显的感觉,就是西方人普遍比较热情一些,打照面会打招呼,say"hi",但东方人普遍内敛一...
    鱼耗子阅读 76评论 0 0
  • 在《东京一年》里,蒋方舟谈到东京有很多一个人经营的小店,这些小店往往逼仄简陋,名字连地图上都找不到,收入也仅够维持...
    铱漩娜阅读 352评论 11 7