RAC运用系列(五) MVVM 实现登录界面

前言

在上一篇中
菜鸟用RAC基于AFNetworking 3.0网络的封装搭建记录(四)
自己把网络请求写法想好了,于是开始想用RAC+MVVM的模式准备用在工程中,在写的过程中也能让自己体会RAC做View与Model之间双向绑定的便利。之所以先用在写登录界面中,是因为界面简单,哈哈~,自己可以先易到难。其实在个人看来,一个App中无非是写两种界面:第一种是像登录、注册、修改密码界面样需要客户输入的。第二种就是像TableView样来展示丰富内容不需要用户输入的。
所以后期会想怎么用RAC+MVVM写TableView界面。先摸索登录界面先,走起~

登录界面摸索中

在mvvm模式中主要是view viewModel model之间的双向绑定,而RAC就是这个胶水,负责绑。建立MVVM文件夹,像登录,注册界面纯用户输入型的是没有Model文件的。


登录.png

在登录界面中用MVVM模式想做到的需求是:

  • 对输入手机号密码的简单要求判断(手机号11位,密码大于4位),不符号要求弹提示框
  • 手机密码输入框都有输入时按钮才高亮,可点击
  • 点击按钮后跳转另一界面

界面样子如下。

屏幕快照 2019-04-03 上午9.58.50.png

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的运用
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容