ReactiveCocoa代码实践之-UI组件的RAC信号操作
上一节是自己对网络层的一些重构,本节是自己一些代码小实践做出的一些demo程序,基本涵盖大多数UI控件操作。
一.用UISlider实现调色板
假设我们现在做一个demo,上面有一个View用来展示颜色,下面有三个UISlider滑竿分别控制RGB的色值,随着不同滑竿的拖动上面view的颜色会随之改变。 可以先脑补一下不用RAC该怎么写。 如果使用RAC只需要将三个信号包装起来用适当的操作就能实现。

// 拖线的UI控件
@property (weak, nonatomic) IBOutlet UIView *topView;
@property (weak, nonatomic) IBOutlet UISlider *slider1;
@property (weak, nonatomic) IBOutlet UISlider *slider2;
@property (weak, nonatomic) IBOutlet UISlider *slider3; // viewDidLoad中
// 分别将三个控件的改变都包成一个信号。
RACSignal *s1 = [[self.slider1 rac_newValueChannelWithNilValue:@0]startWith:@0];
RACSignal *s2 = [[self.slider2 rac_newValueChannelWithNilValue:@0]startWith:@0];
RACSignal *s3 = [[self.slider3 rac_newValueChannelWithNilValue:@0]startWith:@0]; RACSignal *threeSignal = [RACSignal combineLatest:@[s1,s2,s3] reduce:^id(NSNumber* value1,NSNumber* value2,NSNumber* value3){
return @[value1,value2,value3];
}]; // 监听这个"合成"后的信号,改变view的颜色
[threeSignal subscribeNext:^(NSArray *arr) {
self.topView.backgroundColor = [UIColor colorWithRed:[arr[0] doubleValue] green:[arr[1] doubleValue] blue:[arr[2] doubleValue] alpha:1];
}];
上面的startWith:@0需要注意,如果不加这个初始值那必须在三个滑竿都动一下才能显示颜色。 上面使用的方法时UISlider专属的,也可以用下面的方法写,这个是UIControl的方法会支持更多其他UI控件。
RACSignal *s1 = [[[self.slider1 rac_signalForControlEvents:UIControlEventValueChanged] map:^id(id value) {
return @(self.slider1.value);
}] startWith:@0];
二.简洁代码实现登录逻辑

在UI控件中难点不多,但是值得注意的就是各种状态的多级管理,如果哪里疏忽了就很容易造成bug,这也就导致很多地方有判断结构,并且各种来回赋值。 假设现在需要做一个登录框,有账号密码和同意条款三项,必须满足账号密码大于2位且选择了同意,才允许注册。 旧的写法非常麻烦,还需要监听valueChange事件等。如果用RAC只需要写如下代码:
@property (weak, nonatomic) IBOutlet UITextField *accountTxt;
@property (weak, nonatomic) IBOutlet UITextField *pwdTxt;
@property (weak, nonatomic) IBOutlet SXSwitch *agreeSw; // 同意条款
@property (weak, nonatomic) IBOutlet UIButton *loginBtn; // 注册按钮 // viewDidLoad方法
self.loginBtn.enabled = NO;
RAC(self.loginBtn , enabled) = [RACSignal combineLatest:@[self.accountTxt.rac_textSignal , self.pwdTxt.rac_textSignal, self.agreeSw.rac_newOnChannel] reduce:^(NSString *account, NSString *pwd, NSNumber *isOn){
return @((account.length > 2)&&(pwd.length >2)&&[isOn boolValue]);
}];
这其中combineLatest数组中用的都是控件专属的信号, 也可以使用RAC(self.agreeSw, on) 这种写法直接把某一个属性的状态用信号传过来。但是这里需要注意:假设你监听了A类的B属性时,只有走了B属性的set方法才会被监听捕获,如果是通过其他方法修改的属性值则无效。 比如UISwitch的来回拨动过程中并没有走on这个属性的set方法。
三.通过interval方法实现时钟
这是一种默认循环的方法,除非你通过控制Disposable把他禁了。 interval这个方法就是传入一个参数是间隔时间,然后内部每隔这一段时间就发一个[NSDate date]的对象,然后block内部把这个date设置一个格式以字符串的方法返回。
RAC(self, timeLabel.text) = [[[RACSignal interval:1 onScheduler:[RACScheduler currentScheduler]] startWith:[NSDate date]] map:^id (NSDate *value) {
NSDateComponents *dateComponents = [[NSCalendar currentCalendar] components:NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond fromDate:value];
return [NSString stringWithFormat:@"%02ld:%02ld:%02ld", (long)dateComponents.hour, (long)dateComponents.minute, (long)dateComponents.second];
}];

四.其他控件事件操作
除了上面的UIButton,UISlider,UIControl的分类方法还有很多操作
UISegmentedControl (RACSignalSupport)分类就为此控件提供了便捷处理方法,相比于常规的监听seg的元素点击事件,再取出当前选中的index。RACSignal可以直接得到需要的值
[[self.seg rac_newSelectedSegmentIndexChannelWithNilValue:@0]subscribeNext:^(id x) {
// 返回的基本数据类型都被装包成NSNumber,可在此做一些判断操作
NSLog(@"selectIndex-%@",x);
}];
UIDatePicker (RACSignalSupport)分类为时间选择框封装了一个操作,每当选框改变时返回NSDate类型
[[picker rac_newDateChannelWithNilValue:[NSDate date]]subscribeNext:^(NSDate *x) {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = @"HH:mm";
dateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"zh_CN"];
NSString *dateStr = [dateFormatter stringFromDate:x];
}];

如果在这里给控件赋值,每一次改动都会让展示控件的值更新,如果有的设计不希望这么频繁只有在点击确认后再将时间显示可以根据自己喜好自行赋值。
除此这些还有很多UI控件绑定的方法 UIAlertView (RACSignalSupport) 里面就提供了一些方法比如点击弹窗按钮可以在subscribeNext里统一处理各个按钮的点击事件。 但是现在UIAlertView已被UIAlertController取代所以,UIAlertView和UIActionSheet这里可以忽略不提。
五.生命周期相关操作
UITableView和UICollectionView的Cell都有重用的机制,如果给这个Cell绑定了一些监听,那这个Cell被重用它子控件的监听该何去何从?UITableViewCell (RACSignalSupport)、UICollectionReusableView (RACSignalSupport)这两个分类里提供了即将重用时的信号rac_prepareForReuseSignal
做过两个类似的场景,一个是tableView的cell回复按钮点击会跳到回复页,一个是collection的item内有个按钮点击就变颜色。
// UITableViewDataSource
- (UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
SXFeedbackCell * cell = [tableView dequeueReusableCellWithIdentifier:@"SXFeedbackCell"];
@weakify(self)
[[[cell.replyButton rac_signalForControlEvents:UIControlEventTouchUpInside] takeUntil:cell.rac_prepareForReuseSignal]
subscribeNext:^(UIButton *x) {
@strongify(self)
// 处理一些其他逻辑
[self.navigationController pushViewController:[SXReplyPage new] animated:YES];
}];
return cell;
} // UICollectionViewDataSource
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell * cell = [collectionView dequeueReusableCellWithIdentifier:@"SXDownloadCell"];
[[[cell.changeBtn rac_signalForControlEvents:UIControlEventTouchUpInside] takeUntil:cell.rac_prepareForReuseSignal]
subscribeNext:^(UIButton *x) {
cell.backgroundColor = [UIColor grayColor];
}];
return cell;
}
其中takeUntil操作是监听某个事件直到什么时候结束。当这个cell即将重用时rac_prepareForReuseSignal到来会触发disposable信号结束监听。
非重用类型的控件的生命周期可以用rac_willDeallocSignal 信号监听,但是在开发中很少会用到此信号,因为大多是信号操作的内部代码里都帮你做了这个操作,即监听一个事件直到自己结束时停止监听。
// rac_textSignal源码
- (RACSignal *)rac_textSignal {
@weakify(self);
return [[[[[RACSignal
defer:^{
@strongify(self);
return [RACSignal return:self];
}]
concat:[self rac_signalForControlEvents:UIControlEventAllEditingEvents]]
map:^(UITextField *x) {
return x.text;
}]
takeUntil:self.rac_willDeallocSignal]
setNameWithFormat:@"%@ -rac_textSignal", self.rac_description];
} // rac_signalForControlEvents源码
- (RACSignal *)rac_signalForControlEvents:(UIControlEvents)controlEvents {
@weakify(self);
return [[RACSignal
createSignal:^(id<RACSubscriber> subscriber) {
@strongify(self);
[self addTarget:subscriber action:@selector(sendNext:) forControlEvents:controlEvents];
[self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
[subscriber sendCompleted];
}]];
return [RACDisposable disposableWithBlock:^{
@strongify(self);
[self removeTarget:subscriber action:@selector(sendNext:) forControlEvents:controlEvents];
}];
}]
setNameWithFormat:@"%@ -rac_signalForControlEvents: %lx", self.rac_description, (unsigned long)controlEvents];
}
有不同见解的地方欢迎吐槽。 本文禁止转载
ReactiveCocoa代码实践之-UI组件的RAC信号操作的更多相关文章
- ReactiveCocoa代码实践之-RAC网络请求重构
前言 RAC相比以往的开发模式主要有以下优点:提供了统一的消息传递机制:提供了多种奇妙且高效的信号操作方法:配合MVVM设计模式和RAC宏绑定减少多端依赖. RAC的理论知识非常深厚,包含有FRP,高 ...
- ReactiveCocoa代码实践之-更多思考
三.ReactiveCocoa代码实践之-更多思考 1. RACObserve()宏形参写法的区别 之前写代码考虑过 RACObserve(self.timeLabel , text) 和 RACOb ...
- 「前端」尚妆 UI 组件库工程实践(weex vue)
本文来自尚妆前端团队南洋 发表于尚妆github博客,欢迎订阅! 前言 尚妆大前端团队使用 weex 进行三端统一开发有一段时间了,截止本文发表「达人店」APP大部分页面都已经用 weex 进行了重构 ...
- Framework7-Vue的UI组件代码
Framework7-Vue提供了一套UI组件库,想要什么效果,直接到上面复制代码即可 http://www.framework7.cn/ 这里有非常多的ui组件,基本上可以满足项目中的大部分需求 h ...
- 加薪攻略之UI组件库实践—storybook
目录 加薪攻略之UI组件库实践-storybook 一.业务背景 二.选用方案 三.引入分析 项目结构 项目效果 四.实现步骤 1.添加依赖 2.添加npm执行脚本 3.添加配置文件 4.添加必要的w ...
- 这是一个比较全的Android UI 组件
Android组件及UI框架大全 原文地址:http://blog.csdn.net/smallnest/article/details/38658593 Android 是目前最流行的移动操作系统 ...
- Android热身:通过网络获取资源并更新UI组件
Android热身:通过网络获取资源并更新UI组件 目标 点击"发送请求"按钮,下载某网页的html源码,并显示在TextView控件上:点击"清空",清除Te ...
- 学习通过Thread+Handler实现非UI线程更新UI组件
[Android线程机制] 出于性能考虑,Android的UI操作并不是线程安全的,这就意味着如果有多个线程并发操作UI组件,可能导致线程安全问题.为了解决这个问题,Android制定了一条简单的规则 ...
- Android UI组件----ListView列表控件详解
[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/3 ...
随机推荐
- Angular2入门系列教程3-多个组件,主从关系
上一篇 Angular2项目初体验-编写自己的第一个组件 好了,前面简单介绍了Angular2的基本开发,并且写了一个非常简单的组件,这篇文章我们将要学会编写多个组件并且有主从关系 现在,假设我们要做 ...
- JavaScript 对象属性介绍
本篇主要介绍JS中对象的属性,包括:属性的分类.访问方式.检测属性.遍历属性以及属性特性等内容. 目录 1. 介绍:描述属性的命名方式.查找路径以及分类 2. 属性的访问方式:介绍'.'访问方式.'[ ...
- MVC5+EF6+MYSQl,使用codeFirst的数据迁移
之前本人在用MVC4+EF5+MYSQL搭建自己的博客.地址:www.seesharply.com;遇到一个问题,就是采用ef的codefirst模式来编写程序,我们一般会在程序开发初期直接在glob ...
- System.FormatException: GUID 应包含带 4 个短划线的 32 位数(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)。
在NHibernate数据库查询中出现了这个错误,由于是数据库是mysql的,当定义的字段为char(36)的时候就会出现这个错误. [解决方法] 将char(36) 改成varchar(40)就行了 ...
- .NET平台开源项目速览(18)C#平台JSON实体类生成器JSON C# Class Generator
去年,我在一篇文章用原始方法解析复杂字符串,json一定要用JsonMapper么?中介绍了简单的JSON解析的问题,那种方法在当时的环境是非常方便的,因为不需要生成实体类,结构很容易解析.但随着业务 ...
- Effective java笔记(二),所有对象的通用方法
Object类的所有非final方法(equals.hashCode.toString.clone.finalize)都要遵守通用约定(general contract),否则其它依赖于这些约定的类( ...
- 防线修建 bzoj 2300
防线修建(1s 512MB)defense [问题描述] 近来A国和B国的矛盾激化,为了预防不测,A国准备修建一条长长的防线,当然修建防线的话,肯定要把需要保护的城市修在防线内部了.可是A国上层现在还 ...
- 看图理解JWT如何用于单点登录
单点登录是我比较喜欢的一个技术解决方案,一方面他能够提高产品使用的便利性,另一方面他分离了各个应用都需要的登录服务,对性能以及工作量都有好处.自从上次研究过JWT如何应用于会话管理,加之以前的项目中也 ...
- HTML学习笔记
HTML学习笔记 2016年12月15日整理 Chapter1 URL(scheme://host.domain:port/path/filename) scheme: 定义因特网服务的类型,常见的为 ...
- Configure a VLAN (on top of a bond) with NetworkManager (nmcli) in RHEL7
not on top of a bond Environment Red Hat Enterprise Linux 7 NetworkManager Issue Need an 802.1q VLAN ...