前言
在上一篇中
菜鸟用RAC基于AFNetworking 3.0网络的封装搭建记录(四)
自己把网络请求写法想好了,于是开始想用RAC+MVVM的模式准备用在工程中,在写的过程中也能让自己体会RAC做View与Model之间双向绑定的便利。之所以先用在写登录界面中,是因为界面简单,哈哈~,自己可以先易到难。其实在个人看来,一个App中无非是写两种界面:第一种是像登录、注册、修改密码界面样需要客户输入的。第二种就是像TableView样来展示丰富内容不需要用户输入的。
所以后期会想怎么用RAC+MVVM写TableView界面。先摸索登录界面先,走起~
登录界面摸索中
在mvvm模式中主要是view viewModel model之间的双向绑定,而RAC就是这个胶水,负责绑。建立MVVM文件夹,像登录,注册界面纯用户输入型的是没有Model文件的。
在登录界面中用MVVM模式想做到的需求是:
- 对输入手机号密码的简单要求判断(手机号11位,密码大于4位),不符号要求弹提示框
- 手机密码输入框都有输入时按钮才高亮,可点击
- 点击按钮后跳转另一界面
界面样子如下。
view的写法很简单Masonry布局,为了更好的体现mvvm,单独写了个view类
STLoginAccountView
。
controller里面的写法
我发现在写viewmodel的时候,有时不知道需要提供给controller什么,因为用mvvm模式就是想得太多,所以个人觉得先大概的实现controller逻辑,到时觉得controller需要什么时,再在viewmodel中添加这样好点。
controller的写法有持有两个对象,一个View,一个ViewModel
@interface STLoginViewController ()
@property (nonatomic,strong) STLoginAccountView * loginView;
@property (nonatomic,strong) STLoginViewModel * loginViewModel;
@end
第一步view布局
-(void)setupSubView
{
_loginView = [[STLoginAccountView alloc] initWithFrame:CGRectMake(0, 150, self.view.bounds.size.width, 250)];
[self.view addSubview:_loginView];
[self bindViewModel];
}
第二步绑定用到数据
-(void)bindViewModel
{
//绑定手机号 这样写可以self.loginView.PhoneTF.text = 复制时也能调用
RAC(self.loginViewModel,phoneNumber ) = [RACSignal merge:@[RACObserve(self.loginView.phoneTF, text),self.loginView.phoneTF.rac_textSignal]];
//绑定密码
RAC(self.loginViewModel,password) = [RACSignal merge:@[RACObserve(self.loginView.psdTF, text),self.loginView.psdTF.rac_textSignal]];
//绑定登录按钮的使能
RAC(self.loginView.loginBtn,enabled) = self.loginViewModel.loginEnableSignal;
@weakify(self);
//按钮点击
[[[self.loginView.loginBtn rac_signalForControlEvents:UIControlEventTouchUpInside] doNext:^(__kindof UIControl * _Nullable x) {
@strongify(self);
//这里可以加转转,但登录界面输入框有格式校验
}] subscribeNext:^( UIButton * sender) {
@strongify(self)
//对手机账号进行判断
if (self.loginViewModel.phoneNumber.length != 11) {
[CMTipsView showTips:@"请输入正确的手机号"];
return ;
}
if (self.loginViewModel.password.length < 4) {
[CMTipsView showTips:@"请输入正确的密码"];
return ;
}
NSLog(@"执行到了这里");
[CMTipsView showProgressHud:@"正在加载……" addToView:self.view];
[self.loginViewModel.loginCommand execute:nil];
}];
/*这种按钮的写法也是一样的
[[self.loginView.loginBtn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
//对手机账号进行判断
if (self.loginViewModel.phoneNumber.length != 11) {
[CMTipsView showTips:@"请输入正确的手机号"];
return ;
}
if (self.loginViewModel.password.length < 4) {
[CMTipsView showTips:@"请输入正确的密码"];
return;
}
NSLog(@"执行到了这里");
[CMTipsView showProgressHud:@"正在加载……" addToView:self.view];
[self.loginViewModel.loginCommand execute:nil];
}];*/
//登录按钮颜色改变
RAC(self.loginView.loginBtn,backgroundColor) = [RACSignal combineLatest:@[self.loginView.phoneTF.rac_textSignal,self.loginView.psdTF.rac_textSignal] reduce:^id _Nonnull(NSString * account,NSString * psd){
UIColor * color = [UIColor grayColor];
if (account.length&& psd.length) {
color = [UIColor orangeColor];
}
return color;
}];
//登录请求数据成功
[self.loginViewModel.loginCommand.executionSignals.switchToLatest subscribeNext:^(id _Nullable x) {
@strongify(self);
NSLog(@"执行成功了");
[CMTipsView hideHUDForView:self.view];
TestViewController * testVc= [[TestViewController alloc] init];
[self.navigationController pushViewController:testVc animated:YES];
}];
//数据失败
[self.loginViewModel.loginCommand.errors subscribeNext:^(NSError * _Nullable x) {
@strongify(self);
[CMTipsView hideHUDForView:self.view];
[CMTipsView showTips:x.userInfo[@"msg"]];
}];
}
在绑定的时候觉得有些注意的地方是:RAC(self.loginViewModel,phoneNumber ) = [RACSignal merge:@[RACObserve(self.loginView.phoneTF, text),self.loginView.phoneTF.rac_textSignal]];
这里。
ViewModel
在写viewmodel时要有明确的目标,知道自己要提供什么。如在.h中:
@interface STLoginViewModel : NSObject
@property (nonatomic,copy) NSString * phoneNumber;
@property (nonatomic,copy) NSString * password;
@property (nonatomic,strong) RACCommand * loginCommand; //登录执行的命令
@property (nonatomic,strong) RACSignal * loginEnableSignal ; //按扭点击是否有效的信号
@end
.m的实现,
-(instancetype)init
{
self = [super init];
if (self) {
[self createRACSignal];
}
return self;
}
-(void)createRACSignal
{
@weakify(self);
self.loginEnableSignal = [[RACSignal combineLatest:@[RACObserve(self, phoneNumber),RACObserve(self, password)] reduce:^id _Nonnull(NSString * phone,NSString * psd ){
return @(phone.length > 0 && psd.length > 0);
}] distinctUntilChanged];
self.loginCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id _Nullable input) {
@strongify(self);
@weakify(self);
//模拟网络请求
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
@strongify(self);
@weakify(self);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)5.0*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
NSLog(@"模拟成功的请求");
[subscriber sendNext:nil];
[subscriber sendCompleted];
});
return nil;
}];
// return
}];
}
需要注意的是distinctUntilChanged 运用
总结
- 在写时有些注意使用的细节问题。
- RACCommand的运用