页面传参
一、前言
有关视图跳转详参博文《iOS开发进阶(十一):ViewController 控制器详解》。本篇博文继续讲解视图跳转参数如何传递与接收。
界面跳转传值实际上就是将数据从一个界面传送给另一个界面,本文中的传值基于UINavigationController
实现,首先在AppDelegate
里面创建一个UINavigationController
,把ViewController
作为其根视图。代码如下:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
UINavigationController *nav = [[UINavigationController alloc]initWithRootViewController:[ViewController new]];
self.window.rootViewController = nav;
nav.navigationBar.translucent = NO;
[self.windowmakeKeyAndVisible];
return YES;
}
新建一个继承于UIViewController
的视图控制器,命名为NextViewController
,为方便讲解,令ViewController
为A,NextViewController为B。并在两个控制器分别放上一个button和一个label,label作为属性,button增加一个点击事件。
在ViewDidLoad
中加入如下代码:
self.view.backgroundColor = [UIColor blueColor];
UIButton *btnProperty = [UIButton buttonWithType:UIButtonTypeRoundedRect];
btnProperty.frame = CGRectMake(100,60, 175, 40);
[btnProperty setTitle:@"属性传值"forState:UIControlStateNormal];
[btnProperty addTarget:self action:@selector(btnProperty)forControlEvents:UIControlEventTouchUpInside];
[btnProperty setBackgroundColor:[UIColorgreenColor]];
[self.view addSubview:btnProperty];
[self.view addSubview:self.lblProperty];
另外给self.lblProperty
做一个懒加载:
- (UILabel *)lblProperty{
if (!_lblProperty) {
_lblProperty = [[UILabel alloc]initWithFrame:CGRectMake(100,20, 175, 30)];
_lblProperty.backgroundColor = [UIColor whiteColor];
}
return _lblProperty;
}
在NextViewController也加入相同代码,只需把界面颜色换一下区分开即可;
二、属性传值
属性传值即给相应的ViewController
定义一个外部可用的属性,在界面跳转时传送相应的值,所有传值都可分为A–>B和B–>A,其中A为源视图,B为目标视图。
2.1 属性传值A–>B
通过属性由A传值到B不难理解,只需要在B界面定义一个属性,在A PUSH到B的时候把需要传送的内容赋值给B对应的属性即可,代码如下:
-
在NextViewController.h 定义一个属性(本文以字符串为例):
@property (nonatomic,strong) NSString *strProperty2;
-
在ViewController中的点击事件做如下操作:
//属性传值 - (void)btnProperty{ NextViewController *nextVC = [[NextViewControlleralloc]init]; nextVC.strProperty2 = @"shq5785"; [self.navigationController pushViewController:nextVC animated:YES]; }
-
这时B控制器已经拿到了A传来的值,只需要在
viewDidLoad
做个赋值操作即可展现出传过来的字符串了://属性传值 self.lblProperty.text =self.strProperty2;
2.2 属性传值B–>A
属性由B到A传值会比A到B稍微复杂一点,不过也是分为三步:
-
给A定义一个外部可用属性:
@property (nonatomic,strong) NSString *strProperty1;
-
在B跳转回A的点击事件中找到A控制器并给其属性赋值,这里拿到A控制器就不能跟A–>B那样直接新建一个控制器了,因为A控制器还存在,只是没有显示出来而已;所以应该通过导航栏控制器中获取当前A控制器:
- (void)btnProperty{ ViewController *VC =self.navigationController.viewControllers[0]; VC.strProperty1 = @"iOS"; [self.navigationController popViewControllerAnimated:YES]; }
-
这时A中已经拿到了B传过来的值,但是要显示的话应该在
viewWillAppear
中处理:- (void)viewWillAppear:(BOOL)animated{ // 属性传值 self.lblProperty.text = self.strProperty1; }
三、代理传值
3.1 代理传值A–>B
3.1 在A控制器的头文件申明协议:
@protocol ViewControllerDelegate <NSObject>
@optional
- (void)delegateSendToNext:(NSString * )strDelegate;
@end
-
在
@interface
中添加遵循协议的代理属性@property (nonatomic,assign)id<ViewControllerDelegate> delegate;
-
在B的头文件中遵循A的协议;
@interface NextViewController :UIViewController<ViewControllerDelegate>
-
在A跳转到B的点击事件中,让B界面成为A的代理,并调用代理方法;
//代理传值
- (void)btnDelegate{
NextViewController *nextVC = [[NextViewController alloc]init];
self.delegate = nextVC;
[self.delegate delegateSendToNext:@"shq5785"];
[self.navigationController pushViewController:nextVC animated:YES];
}
-
在B控制器实现A协议中的方法完成传值。
//在B控制器实现A控制器中的代理方法 - (void)delegateSendToNext:(NSString * )strDelegate{ self.lblDelegate.text = strDelegate; }
3.2 代理B–>A
-
在B控制器头文件申明协议;
@protocol NextViewControllerDelegate <NSObject> @optional - (void)delegateSend:(NSString *)strDelegate; @end
-
在B控制器定义遵循协议的属性;
@property (nonatomic,assign) id<NextViewControllerDelegate> delegate;
-
在B跳回A的点击事件中调用代理方法;
- (void)btnDelegate{ [self.delegate delegateSend:@"iOS"]; [self.navigationController popViewControllerAnimated:YES]; }
-
A遵循B的协议;
@interface ViewController ()<NextViewControllerDelegate>
-
在A跳转到B的点击事件中使A成为B的代理者;
//代理传值 - (void)btnDelegate{ NextViewController *nextVC = [[NextViewController alloc]init]; nextVC.delegate = self; [self.navigationController pushViewController:nextVC animated:YES]; }
-
在A控制器实现B协议中的代理方法;
//代理方法 - (void)delegateSend:(NSString *)strDelegate{ self.lblDelegate.text = strDelegate; }
四、通知传值
通知传值的核心部分,就是建立好监听者与发送者在时间上的关系,即一定要先有监听者再去发送通知,掌握好了这一点就可以玩转通知传值。
4.1 通知传值A–>B
-
B是消息的接收者,要在A发送消息之前先建立好监听者,这时候就需要在B中重写
init
方法来达到这个目的,然后写收到通知后的操作,最后一定要记得移除监听者,否则会造成内存泄漏;- (instancetype)init{ if (self = [superinit]) { //创建监听者 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changeValue:) name:@"bobo" object:nil]; } return self; } //通知传值 - (void)changeValue:(NSNotification *)notification{ NSDictionary * dict = notification.userInfo; self.lblNotification.text = dict[@"key"]; } //移除监听者 - (void)dealloc{ [[NSNotificationCenter defaultCenter] removeObserver:self name:@"bobo" object:nil]; }
-
接下来就是在A的点击事件中添加发送消息即可。
// 通知传值 - (void)btnNotification{ NextViewController *nextVC = [[NextViewController alloc]init]; NSNotification *notification = [[NSNotification alloc]initWithName:@"bobo" object:nil userInfo:@{@"key":@"shq5785"}]; // 发送通知 [[NSNotificationCenter defaultCenter] postNotification:notification]; [self.navigationController pushViewController:nextVC animated:YES]; }
4.2 通知传值B–>A
-
由B到A传值就更简单了,因为时间顺序上一定是先有A后有B,所以只要在A的
viewDidLoad
中建立一个监听者,然后实现监听事件,最后一处监听者就行//在viewDidLoad中注册监听者 [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(changeValue:) name:@"bobo2" object:nil]; //收到通知后操作 - (void)changeValue:(NSNotification *)notification{ NSDictionary *dict = notification.userInfo; self.lblNotification.text = dict[@"key"]; } //移除监听者 - (void)dealloc{ [[NSNotificationCenter defaultCenter]removeObserver:self name:@"bobo2" object:nil]; }
-
在B跳转回A的时候发送通知就行了。
- (void)btnNotification{ NSNotification * notification = [[NSNotification alloc]initWithName:@"bobo2" object:nil userInfo:@{@"key":@"iOS"}]; //发送通知 [[NSNotificationCenter defaultCenter] postNotification:notification]; [self.navigationController popViewControllerAnimated:YES]; }
五、单例传值
单例,就是从程序开始到程序结束不管中途怎么去实例化都只有一个实例对象,先创建一个单例(本文已Teacher类作为单例示例):
//Teacher.h
#import <Foundation/Foundation.h>
@interface Teacher :NSObject<NSCopying,NSMutableCopying>
@property (nonatomic,strong) NSString * name;
@property (nonatomic,assign) int age;
+ (instancetype)shareInstance;
@end
//Teacher.m
#import "Teacher.h"
staticTeacher *teacher = nil;
@implementation Teacher
+ (instancetype)shareInstance{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
teacher = [[Teacher alloc]init];//确保对象只被初始化一次
});
return teacher;
}
+ (instancetype)allocWithZone:(struct_NSZone *)zone{
if (teacher ==nil) {
teacher = [[superallocWithZone:zone]init];
}
return teacher;
}
- (instancetype)copyWithZone:(NSZone *)zone{
return teacher;
}
- (id)mutableCopyWithZone:(nullableNSZone *)zone{
return teacher;
}
@end
5.1 单例传值A–>B
-
单例传值只要在A中拿到单例对象,给对象属性赋值,都在跳转事件执行;
//单例传值 - (void)btnInstance{ NextViewController *nextVC = [[NextViewController alloc]init]; Teacher *teacher = [Teacher new]; teacher.name = @"SHQ5785"; [self.navigationController pushViewController:nextVC animated:YES]; }
-
在B界面拿到单例对象在给label赋值就行,在
viewDidLoad
写如下代码即可://单例传值 Teacher *teacher = [Teacher new]; self.lblInstance.text = teacher.name;
5.2 单例传值B–>A
-
由B界面跳回A界面也是跟A一样在跳转事件给单例对象赋值:
- (void)btnInstance{ //单例传值 Teacher *teacher = [Teacher new]; teacher.name =@"iOS"; [self.navigationController popViewControllerAnimated:YES]; }
-
但是A界面拿到单例值需要在
viewWillAppear
中去赋值:- (void)viewWillAppear:(BOOL)animated{ // 单例传值 Teacher *teacher = [Teacher new]; self.lblInstance.text = teacher.name; }
六、代码块传值
代码块传值很好用很方便,但是理解上需要花点功夫,接下来咱就一步一步去分析完成;
6.1 代码块传值A–>B
-
代码块由A传值到B,需要先在B的头文件中定义一个外部可用代码块属性;
@property (nonatomic,strong) void(^oneBlock)(NSString *) ;
-
然后B给代码块赋值,但是赋值一定要在A中调用之前完成,因此也需要重写B的
init
方法- (instancetype)init{ if (self = [superinit]) { //代码块传值a-->b NextViewController *nextVC = self; _oneBlock = ^(NSString *strBlock){ nextVC.lblBlock.text = strBlock; }; } return self; }
-
在A跳转到B的点击事件中调用代码块;
//代码块传值 - (void)btnBlock{ NextViewController * nextVC = [[NextViewController alloc]init]; //a-->b if (nextVC.oneBlock) {//调用代码块前一定要先判断代码块是否为空,调用空的代码块会直接使系统奔溃 //跳回主线程调用代码块 dispatch_async(dispatch_get_main_queue(), ^{ nextVC.oneBlock(@"SHQ5785"); }); } [self.navigationController pushViewController:nextVC animated:YES]; }
6.2 代码块传值B–>A
-
代码块传值由B到A,也需要在B的头文件中定义一个外部可用代码块属性;
@property (nonatomic,strong) void(^myBlock)(NSString *) ;
-
不过这时候代码块赋值操作应该在A的点击事件中进行,因为需要在B中调用代码块传送一个字符串回到A中;
//代码块传值 - (void)btnBlock{ NextViewController *nextVC = [[NextViewController alloc] init]; //b-->a nextVC.myBlock = ^(NSString *strBlock){ self.lblBlock.text = strBlock ; }; [self.navigationController pushViewController:nextVC animated:YES]; }
-
在B跳转回A点击事件中调用代码块完成传值。
- (void)btnBlock{ //代码块传值b-->a if (_myBlock) { dispatch_async(dispatch_get_main_queue(), ^{ _myBlock(@"iOS"); }); } [self.navigationController popViewControllerAnimated:YES]; }
界面跳转传值总结
(1)一般A–>B使用属性传值,单例传值一般可用于A跳转到多个界面,多个界面都需要用到A的值的情况;
(2)一般B–>A使用代码块或者代理传值更为方便,使用通知传值也很方便,不过一般情况不推荐使用通知传值
,通知传值存在内存泄漏风险,用多了有可能导致系统奔溃。
七、拓展阅读
- 点赞
- 收藏
- 关注作者
评论(0)