「OC」多界面传值

「OC」多界面传值


在仿写网易云的过程之中,为了实现多界面传值,我了解了委托,通知和属性等方式,并分别实现了夜间模式的传值,为了后面3Gshared的需要,特此对多界面传值进行学习与总结。

属性传值

属性的传值一般来说是从前往后进行的

这个的实现比较简单,我们可以在视图A之中实现一个事件,然后将信息发送给后一个视图B并且控制视图弹出。以下是我用searchBar完成的一个界面传值功能,当我们在控制器A之中点击搜索时,就会将searchBar之中的文字内容通过控制器B的属性传给控制器B,实现了属性传值。

#import "ViewController.h"
#import "ViewController2.h"
@interface ViewController ()<UISearchBarDelegate>

@property (nonatomic, strong)UISearchBar *searchBar;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.searchBar = [[UISearchBar alloc]init];
    self.searchBar.frame = CGRectMake(100, 100, 200, 200);
    self.searchBar.delegate = self;
    [self.view addSubview:_searchBar];
}
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
    
    NSLog(@"1");
    ViewController2 *controller2 = [[ViewController2 alloc] init];
    controller2.text = searchBar.text;
    // 执行搜索操作
    [self presentViewController:controller2 animated:YES completion:nil];
}

-(void) touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    //点击空白处,虚拟键盘回收,不再作为第一响应者
    [self.searchBar resignFirstResponder];
}
@end
—————————————————————————————————————————————————————————————————————————————————————————————————————————#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface ViewController2 : UIViewController

@property (nonatomic,strong) NSString *text;
@end

NS_ASSUME_NONNULL_END
#import "ViewController2.h"

@interface ViewController2 ()


@end

@implementation ViewController2

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor redColor];
    UILabel *label = [[UILabel alloc] init];
    label.text = self.text;
    label.frame = CGRectMake(100, 100, 200, 200);
    [self.view addSubview:label];
    
    
}

Jul-18-2024 10-44-06

协议/代理传值

协议传值多用于后一个控制器向前一个控制器进行传值。

拿我实现的夜间模式来举例,我们要创建一个协议来规定协议之中的方法,便于后一个视图B调用控制器A之中方法

@protocol NightModeChangeDelegate <NSObject>

-(void)userDidChangeDarkModeSetting:(BOOL)nightModeEnabled;

@end

之后在控制器B之中添加一个代理对象

在这里插入图片描述

在视图B之中的UISwitch的状态改变之时,我们就直接触发视图A的相关方法

image-20240718110521498

我们还需要先将视图B的代理对象赋给视图A,我在SceneDelegate.m进行了实现

JCFIrst *first = [[JCFIrst alloc] init];
UINavigationController *nav1 = [[UINavigationController alloc] initWithRootViewController:first];
nav1.navigationBar.tintColor = [UIColor redColor];
nav1.tabBarItem.title = @"主页";
nav1.tabBarItem.image = [UIImage systemImageNamed:@"house"];

JCThird *profileVC = [[JCThird alloc] init];
profileVC.title = @"我的";
profileVC.view.backgroundColor = [UIColor whiteColor];
UINavigationController *nav3 = [[UINavigationController alloc] initWithRootViewController:profileVC];
nav3.tabBarItem.title = @"我的";
nav3.tabBarItem.image = [UIImage systemImageNamed:@"person"];
    
profileVC.delegate = first;

注:我们还要在JCFIrst.h设置这个视图实现了NightModeChangeDelegate之中的方法

接下来就是在JCFIrst.h写代理方法

- (void)userDidChangeDarkModeSetting:(BOOL)nightModeEnabled

我们就成功的使用协议传值实现了夜间模式

block传值

我感觉block传值与协议传值类似,都是一种回调的思想,与协议不同的是,block的传值可以实现一对多的思路,其实针对简单的单一的传值使用block相较于协议传值来说更加的方便快捷,直接就在对象之间传值,不需要额外的代理

下面是实现过程:

我们先定义一个block,而后在后一个控制器之中声明一个block的属性,

@interface SendingViewController : UIViewController
typedef void (^ValuePassBlock)(NSString *value);
@property (nonatomic, copy) ValuePassBlock valuePassBlock;

@end
 
@implementation SendingViewController

- (void)sendValueToReceivingViewController {
    // 假设要传递的值是一个字符串
    NSString *valueToSend = @"Hello, World!";
    
    // 调用 Block 并传递值
    if (self.valuePassBlock) {
        self.valuePassBlock(valueToSend);
    }
}

@end

然后前面的视图做一个接收,就用block完成了一个跨界面传值

@interface FirstViewController : UIViewController

@end

@implementation FirstViewController

- (void)presentSecondViewController {
    SendingViewController *secondViewController = [[SendingViewController alloc] init];
    
    SendingViewController.valuePassBlock  = ^(NSString *value) {
        NSLog(@"%@",value);
    };
    
    [self presentViewController:secondViewController animated:YES completion:nil];
}

@end

通知传值

iOS中的NSNotificationCenter是一种广泛使用的通信机制,它允许不同组件、模块或应用程序间以一种松耦合的方式传递信息。通知机制的核心是NSNotificationCenter(通知中心),它作为中介,负责发布(post)和分发(deliver)通知。下面阐述通知的运行步骤原理:

我们仍然以夜间模式的实现来举例

1.在发送者中创建并发送通知

在UISwitch的状态发生变化的时候,事件响应,消息中心发送通知,我们将布尔值通过字典打包起来,通过以下方法发生通知

NSDictionary *userInfo = @{@"boolValue": @(isOn)};
        [[NSNotificationCenter defaultCenter] postNotificationName:@"NotificationName" object:nil userInfo:userInfo];

在上述代码中,使用 postNotificationName:object:userInfo: 方法来发送通知。我们需要指定通知的名称(NotificationName)和可选的用户信息(userInfo)。

2.在接收者中注册观察者

我们在需要接受通知的视图控制器之中的 -(void)viewDidLoad设置观察者,设置监听 name为"NotificationName"的发送者

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setNightMode:) name:@"NotificationName" object:nil];

3.在接收者中接受通知

-(void)setNightMode:(NSNotification *)notification {
    NSDictionary *userInfo = notification.userInfo;
    _isOn = [userInfo[@"boolValue"] boolValue];
    if (!_isOn) {
				//进行夜间模式的相关操作
    } else {
       
    }
    [self.view setNeedsDisplay];
    [self.tableView reloadData];
}

4.在使用完或不使用时销毁通知

一般来说我们在合适的位置将观察值释放,这个位置一般是dealloc方法之中

// 在对象销毁或不再需要监听通知的地方
- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"NotificationName" object:nil];
}

总而言之,通过 NSNotificationCenter,我们可以在应用程序内的不同对象之间实现松耦合的通信,方便地进行消息传递和处理。它的方便之处其实是在于,一个通知中心信息的发放,每个接收器都能够接受的到,而NSNotificationCenter之中的name其实就是用来区分不同的发送者的,所以我们在实现消息传值的时候都要注意发送者和接受者name是否相同。

KVO传值

与通知的方法有一点像,我们一共也需要进行观察者的注册,实现监听方法,取消观察。

  1. 观察者的注册

对于KVO之中观察者的注册我们使用以下方法

 (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;

其中keyPath是监听对象之中存在的一个属性名称,NSKeyValueObservingOptions为一个枚举常量:

  • NSKeyValueObservingOptionNew: 当属性的值发生变化时,提供新的属性值作为通知的参数。
  • NSKeyValueObservingOptionOld: 当属性的值发生变化时,提供旧的属性值作为通知的参数。
  • NSKeyValueObservingOptionInitial: 在添加观察者时,立即发送一次通知,提供当前属性的值作为通知的参数。
  • NSKeyValueObservingOptionPrior: 在属性值发生实际变化之前,先发送一次通知,提供旧的属性值作为通知的参数。

context: 这是一个指针类型的参数,用于传递额外的上下文信息。通常情况下可以传入 NULL,表示不需要传递上下文信息。

  1. 实现监听方法,我们实现监听需要显示监听方法(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context,这个方法会在被观察到属性发生变化的时候调用
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    if ([keyPath isEqualToString:@"propertyName"]) {
        // 处理属性变化的逻辑
        // 通过 change 字典获取变化的新值等信息
    }
}

注:被观察对象的属性需要符合 KVC(Key-Value Coding)规范,即属性需要使用 @property 声明,并且支持 KVO。你可以使用 @objc dynamic 关键字来修饰属性,以确保其支持 KVO。

这个 if 语句用于确保在 observeValueForKeyPath:ofObject:change:context:方法中处理的是指定的属性变化。

当注册观察者时,可以指定一个或多个要观察的属性名(KeyPath)。当这些属性中的任何一个发生变化时,KVO 会调用观察者对象的 observeValueForKeyPath:ofObject:change:context: 方法,并传递相关的参数。

所以我们可以通过 keyPath 参数来判断触发回调的是哪个属性的变化。这样,可以实现根据不同的属性变化执行不同的逻辑。

  • KVO机制只能对继承自NSObject的类的属性进行观察,无法对结构体(struct)等非NSObject子类的属性进行观察。
  • 在使用KVO时,需要注意正确地添加和移除观察者,以避免引发内存管理问题,如循环引用。

自动实现KVO监听

一般来说我们监听一个对象的属性,就简单使用监听属性setter方法,让kvo自动实现就行

至于用KVO的方式实现跨界面传值较为繁琐,以下是实现方法,我们监听控制器2的一个属性,当这个属性发生改变之后,就将改变的值在监听者这里获取。

#import "ViewController.h"
#import "ViewController2.h"
@interface ViewController ()<UISearchBarDelegate>

@property (nonatomic, strong)UISearchBar *searchBar;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.searchBar = [[UISearchBar alloc]init];
    self.searchBar.frame = CGRectMake(100, 100, 200, 200);
    self.searchBar.delegate = self;
    [self.view addSubview:_searchBar];
    
    self.l = [[UILabel alloc] init];
    self.l.backgroundColor = [UIColor whiteColor];
    self.l.frame = CGRectMake(100, 300, 200, 35);
    [self.view addSubview:self.l];
}

- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
    ViewController2 *controller2 = [[ViewController2 alloc] init];
    controller2.text = searchBar.text;
    [controller2 addObserver:self forKeyPath:@"context" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
    // 执行搜索操作
    
   
    [self presentViewController:controller2 animated:YES completion:nil];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([keyPath isEqual:@"context"]) {
        self.l.text = [change objectForKey:@"new"];

    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

-(void) touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self.searchBar resignFirstResponder];
}
@end

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN
@interface ViewController2 : UIViewController
@property (nonatomic,strong) NSString *context;
@property (nonatomic,strong) NSString *text;
@property (strong, nonatomic) UITextField *textField;
@property (strong, nonatomic) UIButton *backButton;
@end

NS_ASSUME_NONNULL_END


#import "ViewController2.h"

@interface ViewController2 ()

@property (strong, nonatomic) UITextField *textField;
@property (strong, nonatomic) UIButton *backButton;

@end

@implementation ViewController2

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor redColor];
    UILabel *label = [[UILabel alloc] init];
    label.text = self.text;
    label.frame = CGRectMake(100, 100, 200, 200);
    [self.view addSubview:label];
    
    
    self.view.backgroundColor = [UIColor orangeColor];
    self.backButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    self.backButton.frame = CGRectMake(100, 100, 44, 44);
    self.backButton.backgroundColor = [UIColor blueColor];
    [self.backButton addTarget:self action:@selector(pressBack) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:self.backButton];
    
    self.textField = [[UITextField alloc] initWithFrame:CGRectMake(100, 250, 200, 50)];
    self.textField.keyboardType = UIKeyboardTypeDefault;
    self.textField.borderStyle = UITextBorderStyleRoundedRect;
    [self.view addSubview:self.textField];
}

- (void) pressBack {
    self.context = self.textField.text;
    [self dismissViewControllerAnimated:YES completion:nil];
}



@end

可以看到使用KVO监听实现跨界面回传,相对还是比较麻烦的,所以我们还是多用通知传值

手动触发KVO

虽然KVO主要由setter触发,但也可以通过willChangeValue(forKey:)didChangeValue((forKey:),

willChangeValueForKey::在属性发生变化之前调用,通知KVO开始监听属性的变化。
didChangeValueForKey::在属性发生变化后调用,通知KVO属性的变化已经完成。

当我们需要监听可变数组或者可变集合之中的内容变化时(即在可变数组/集合添加元素),KVO并不会进行自动触发,需要我们用手动的方式进行触发,以下是一个简单的例子

#import "ViewController.h"
#import "ViewController2.h"

@interface ViewController () <UISearchBarDelegate>

@property (nonatomic, strong) NSMutableArray *myArray;
@property (nonatomic, strong) UILabel *label;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.myArray = [NSMutableArray array];
    
    [self addObserver:self forKeyPath:@"myArray" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
    
    UIButton *addButton = [UIButton buttonWithType:UIButtonTypeSystem];
    [addButton setTitle:@"添加元素" forState:UIControlStateNormal];
    [addButton addTarget:self action:@selector(addValueToMyArray) forControlEvents:UIControlEventTouchUpInside];
    addButton.frame = CGRectMake(20, 100, 200, 40);
    [self.view addSubview:addButton];
    
    self.label = [[UILabel alloc] initWithFrame:CGRectMake(20, 200, 200, 40)];
    self.label.text = @"";
    [self.view addSubview:self.label];
}

- (void)addValueToMyArray {
    [self willChangeValueForKey:@"myArray"];
    [self.myArray addObject:@(self.myArray.count + 1)];
    [self didChangeValueForKey:@"myArray"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"1");
    if ([keyPath isEqualToString:@"myArray"]) {
        [self updateLabel];
    }
}

- (void)updateLabel {
    NSString *arrayContent = [self.myArray componentsJoinedByString:@", "];
    self.label.text = arrayContent;
}

- (void)dealloc {
    [self.myArray removeObserver:self forKeyPath:@"array"];
}

@end

结果如下

Sep-08-2024 13-59-56

有兴趣的读者可以自行尝试如果删去手动触发kvo的那两行代码,就会发现程序没有反应。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值