在任何的软件开发中都离不开界面与界面之间的通信,界面通信的最直接的方法就是界面传值.
在开发过程中我们在页面传值时我们通常使用的方法有:属性传值法,block传值法,代理传值法,以及单例传值法,通知传值法
属性传值
属性传值多用于在将前一个页面的值传到后一个页面去,也就是我们通常说的从前往后传值
当第一个页面push到第二个页面的时候,我们在第二个页面声明一个属性用于接受从第一个页面传递过去的值,然后在push这个事件被触发的时候进行赋值,也就是说,先初始化创建第二个控制器(页面),然后通过创建的控制器来访问它所对应的属性,将即将传递的值赋给它,这样就完成了属性传值.于是当页面(控制器)被push到第二个页面之后我们访问它的属性的时候,也就顺便获取到了传递过来的值.
例如:现在有两个控制器FirstViewController和SecondViewController.我们在FirstViewController中创建一个UITextFiled,在SecondViewController中创建一个UILabel,然后在textField中输入文字,我们使用导航控制器来控制页面之间的跳转,当我们跳转到第二个页面的时候,将在textField中输入的文字传递给label,作为label上的文字.
代码如下:
FirstViewController中:
self.navigationItem.title = @"First";
// 设置控制器的背景颜色
self.view.backgroundColor = [UIColor yellowColor];
// 设置控制器的右按钮,并创建事件点击调到下一页面
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"第二页" style:UIBarButtonItemStyleDone target:self action:@selector(next:)];
// 创建输入框
self.firstTF = [[UITextField alloc] initWithFrame:CGRectMake(50, 100, 314, 40)];
self.firstTF.placeholder = @"请输入文字!";
_firstTF.borderStyle = UITextBorderStyleLine;
[self.view addSubview:_firstTF];
SecondViewController中:
self.navigationItem.title = @"Second";
self.view.backgroundColor = [UIColor cyanColor];
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"返回" style:UIBarButtonItemStyleDone target:self action:@selector(back:)];
self.secondLabel = [[UILabel alloc] initWithFrame:CGRectMake(50, 100, 314, 40)];
self.secondLabel.backgroundColor = [UIColor orangeColor];
// 由上一个页面通过属性传值传过来
self.secondLabel.text = _tfString;
[self.view addSubview:_secondLabel];
我们在SecondViewController的.h文件中设置一个字符串属性用来接收从FirstViewController中传递过来的值.
@property(nonatomic,strong)NSString *tfString;
那么我们在什么时候传递这个值呢?
合适的地方应该是在第一个页面跳转到第二个页面的时候创建,也就是在push方法执行之前进行传值.我们在FirsrViewController的右按钮点击事件也就是next方法中进行传值.
-(void)next:(UINavigationController *)sender{
ScondViewController *secondNC = [[ScondViewController alloc] init];
// 将第一个界面的输入框信息赋值给第二个页面的Label
secondNC.tfString = _firstTF.text;
[self.navigationController pushViewController:secondNC animated:YES];
}
这样就实现了从前往后传值,也就是属性传值
代理传值
代理传值多用于从后往前传值.
我们还是使用上面的额例子,比如我们现在在SecondViewController中进行的相关的操作,现在我们要返回到第一个页面,但是我们现在有一个需求就是需要在SecondViewController中创建一个UITextField,在FirstViewController中创建一个UILabel,当页面pop回第一个页面的时候将SecondViewController中textField中的文字传递到FirstViewController中的label上,这个时候,我们就可以使用代理传值.
我们在SecondViewController的.h中创建一个协议,并且设置代理属性,让其遵循该协议.
代码如下:
// 设置一个协议方法
@protocol SecondVCDelegate
// 代理传值
- (void)passValue:(NSString *)value;
@end
// 声明代理属性进行代理传值
@property(nonatomic,weak)id delegate;
在SecondViewController中创建textField
self.secondTF = [[UITextField alloc] initWithFrame:CGRectMake(50, 200, 314, 40)];
self.secondTF.placeholder = @"请输入";
self.secondTF.borderStyle = UITextBorderStyleLine;
[self.view addSubview:_secondTF];
在FirstViewController中创建label并让其遵循协议
#import "FirstViewController.h"
#import "ScondViewController.h"
@interface FirstViewController ()
@property(nonatomic,strong)UILabel *firstLab;
@end
创建UILabel
self.firstLab = [[UILabel alloc] initWithFrame:CGRectMake(50, 200, 314, 40)];
self.firstLab.backgroundColor = [UIColor magentaColor];
[self.view addSubview:_firstLab];
我们在上述属性传值代码中已经为SecondViewController的导航控制器设置了右按钮(返回按钮),那么,我们只需要在其触发事件(back:)中让代理去执行传值即可.
代码如下:
- (void)back:(UINavigationController *)sender{
// 代理去执行传值
[_delegate passValue:_secondTF.text];
[self.navigationController popViewControllerAnimated:YES];
}
然后在FirstViewController的next方法中为SecondNC制定其代理为其自身即可.
实现其代理方法
- (void)passValue:(NSString *)value{
_firstLab.text = value;
}
这样我们就可以在第一个页面得到第二个页面的值了.
block传值
block的本质就和其他OC中变量类似,只不过,block中存储的数据是函数体,但是在使用block时完全可以像调用其他函数似的,传入参数,然后得到返回值.关于详细的block,在这里就不赘述了.
在iOS开发中我们用到block进行传值的情况多数情况下也是在从后往前传值.所以我们依然使用上述例子.
使用block传值,首先我们需要在SecondViewController中定义并声明block属性.
// 定义有参无返回值的匿名函数(传递字符串)
typedef void(^MyBlock)(NSString *);
@interface SecondViewController : UIViewController
@property(nonatomic,copy)MyBlock block;
@end
同上,我们将SecondViewController中textField中输入的文字传递到FirstViewController中的label上显示.
我们在SecondViewController的back方法中调用block,并且将在这个控制器的textField中的文字作为block的参数传递给block.
- (void)back:(UINavigationController *)sender{
// 代理去执行传值
self.block(_secondTF.text);
[self.navigationController popViewControllerAnimated:YES];
}
然后我们在FirstViewController的next方法中,也就是alloc出SecondViewController的时候调用SecondViewController的block,实现传值
- (void)back:(UINavigationController *)sender{
__weak typeof(self)temp = self;
secVC.block = ^(NSString *string){
// 通过回调将传进来的字符串赋值给label
temp.firstLab.text = string;
};
[self.navigationController popViewControllerAnimated:YES];
}
在上述代码中,因为block里面不能直接使用属性,实例变量和方法(因为会造成循环引用),所以我们重新用__weak修饰self并重新命名为temp.这样我们就实现了传值.
单例传值
由于单例在内存中只创建一次的并且可以全局访问的属性,我们可以在必要的时候将数据存放在单例的属性中,并且在必要的时候从单例中通过访问其属性进行调用,这样就实现值的传递
首先我们先创建一个单例类DataHandle,在其.h文件中我们使用类方法声明单例创建的方法,并且声明一个字符串属性用来保存值,(因为我们依然使用上述的例子,所以声明字符串,来保存textField中输入的文字,在实际的开发过程中,读者可以自己根据实际情况而定).
@interface DataHandle : NSObject
// 创建单例
+ (instancetype)sharedDataHandle;
@property(nonatomic,strong)NSString *Str;
@end
在.m文件中实现其初始化方法
@implementation DataHandle
// 声明静态区对象的原因,希望程序运行期间,在内存中一直存在,这样对外界来说,可以随时读取数据
static DataHandle *dataHandle = nil;
// 创建单例(全局区)
+ (instancetype)sharedDataHandle{
if (nil == dataHandle) {
// 我们创建单例使用加号方法的原因是因为,在创建之前,无法存在一个实例对象去调用动态方法来创建它本身
dataHandle = [[DataHandle alloc] init];
}
return dataHandle;
}
@end
无论是从FirstViewController中将textField的值传到SecondViewController的label上还是反过来传值.我们只需要在需要的时候通过其类方法(+ (instancetype)sharedDataHandle)来创建出单例对象来,然后将textField的text属性以赋值的方式赋给单例的Srt属性即可
代码如下:
-(void)next:(UINavigationController *)sender{
DataHandle *dataHandle = [DataHandle sharedDataHandle];
ScondViewController *secondNC = [[ScondViewController alloc] init];
// 将第一个界面的输入框信息赋值给第二个页面的Label
dataHandle.Str = _firstTF.text;
[self.navigationController pushViewController:secondNC animated:YES];
}
假使在SecondViewController中我们为label的text赋值,而其值就是FirstViewController中在textField中输入的值,那么我们可以这样写:
DataHandle *dataHandle = [DataHandle sharedDataHandle];
self.secondLabel.text = dataHandle.str;
如上所述就实现了值的传递.
其实单例不知是在两个页面之间进行传递,由于单例的一次创建全局访问的特点,我们可以将值传递到ThreeViewController,FourViewController等等的控制器,相反的也可以反向传递,在此就不在重复了,其实原理都是相同的.
通知传值
通知在iOS开发中占据着非常重要的地位,通常在两个页面关联不大,但是需要传递信息的时候传递,这样就可以通过通知来实现.
我们新建一个控制器来一键切换所有控制器的背景颜色.我们为其取名为:SettingViewController
创建一个UIButton来控制背景颜色的切换.并且为其设置触发事件
在其出发事件中我们发送消息,并且设置通知的name为"change"用来标记通知,设置userInfo,userInfo是一个字典,我们需要将所有控制器的颜色设置为灰色,于是,我们在创建字典的时候其Value值为[UIColor lioghtGrayConlor].
- (void)changeColor:(UIButton *)sender {
// 发送消息
[[NSNotificationCenter defaultCenter] postNotificationName:@"change" object:nil userInfo:@{@"color":[UIColor lightGrayColor]}];
}
这样当我们点击该button的时候,就发送了一条消息.在其他控制器里,我们设置观察者,注册消息.
// 注册消息
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changeTheme:) name:@"change" object:nil];
当收到消息之后,就会执行changeTheme:方法,我们就可在该方法中获取发送的消息中所传递过来的数据(颜色信息),进行控制器背景颜色的改变.
- (void)changeTheme:(NSNotification *)sender{
self.view.backgroundColor = sender.userInfo[@"color"];
}
在所有控制器里都添加观察者注册消息,并且实现changeTheme方法就实现了背景颜色的切换,而sender.userInfo[@"color"]就是从其他控制器中传递过来的值.我们可以改变userInfo这个字典的信息,从而实现其他类型的值的传递.
总结
以上介绍了五种界面通信的方法,由于笔者自身水平有限,其中难免会有疏漏,还请读者指正,如果有不明白的问题,欢迎在评论区留言讨论指教,我将不胜感激,另,转载请注明出处......