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的运用
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容