RXSwift--登录注册那点事

在iOS学习中登录注册是一个万能的可以拿出来实战的demo。接下来我们就从登录开始入手,PS:如果你对RXSwift中的概念和一些常用的函数不清楚可以参考这篇文章(可能打开比较慢请耐心等待)。开始直接上代码。先看一下我们要实现的效果。

loginGif.gif

分析实现:
1.在还没有输入的时候,显示提醒信息
2.输入账号和密码正确的时候隐藏提示信息
2.在账号和密码都输入的时候登录按钮可以点击

1.直接在storyBoard中创建简单的登录界面

简单的登录界面

2.关联好对应的属性,接下来引入我们今天的重点对象

import RxSwift
import RxCocoa

创建一个disposeBag来盛放我们需要管理的资源,然后把新建的监听都放进去,会在适当的时候销毁这些资源。
let disposeBag = DisposeBag()

3.接下来开始对事件的判断和绑定事件

        //判断账号的输入是否可用
        let accountValid:Observable = accountField.rx.text.orEmpty.map{ value in
            return value.characters.count >= 6
        }
        //判断密码的输入是否可用
        let passwordValid:Observable = passwordField.rx.text.orEmpty.map{ value in
            return value.characters.count >= 6
        }

上面orEmpty是判断当前字符串是否为空的,在RXSwift中已经处理了为nil的情况,map函数是在事件流转换的时候,重新生成另一个事件流,在这里是把一个文字事件流映射成一个bool事件流,accountValidpasswordValid都是Observable<Bool>类型

对于账号和密码输入正确与否的一个显示

//账号密码输入的正确与否 绑定到infoLabel的hidden属性上
//绑定显示
      accountValid.bind(to: accountInfoLabel.rx.isHidden).addDisposableTo(disposeBag)
      passwordValid.bind(to: passwordInfoLabel.rx.isHidden).addDisposableTo(disposeBag)

接着就是对于登录按钮的是否可点击的绑定

        //登录按钮的可用与否
        let loginObserver = Observable.combineLatest(accountValid,passwordValid){(account,password) in
            account && password
        }
        //绑定按钮
        loginObserver.bind(to: loginBtn.rx.isEnabled).addDisposableTo(disposeBag)
        loginObserver.subscribe(onNext: { [unowned self] valid in
            self.loginBtn.alpha = valid ? 1 : 0.5
        }).disposed(by: disposeBag)

上面的将账号和密码输入的值与按钮的enable属性相关联,当accountValidtrue,并且passwordValid也为true时,按钮才可点击,同时也修改了按钮的透明度变化

接下来就是按钮点击事件的判断以及对应的方法的执行

loginBtn.rx.tap
            .asObservable()
            .withLatestFrom(loginObserver)
            .do(onNext: {
                [unowned self]_ in
                self.loginBtn.isEnabled = false
                self.view.endEditing(true)
            })
            .subscribeOn(MainScheduler.instance)//主线程
            .subscribe(onNext: {[unowned self]isLogin in
                self.showAlert(message: "开始点击")
                self.loginBtn.isEnabled = true
            })
            .addDisposableTo(disposeBag)//开始释放

按钮的点击事件中绑定的是loginObserver最新的一个流操作,do(onNext)函数是在执行之前对按钮的一个限定,比如网络请求延迟,按钮点击多次,我在按钮第一次点击的时候,就禁用按钮,等到网络请求成功或者失败返回信息的时候再修改按钮可点击的状态,.subscribeOn函数是指定事件流在那个线程中执行,这里指定的是主线程。subscribe(onNext…………这是点击按钮之后执行方法的闭包。 简写也可以写成这个样子哦,这个只是简单处理按钮的点击事件

loginBtn.rx.tap
            .subscribe(onNext: {[unowned self]isLogin in
                self.showAlert(message: "开始点击")
            })
            .addDisposableTo(disposeBag)//开始释放

最后是alertView的一个弹出视图

fileprivate func showAlert(message:String) {
        let action = UIAlertAction.init(title: "确定", style: .default, handler: nil)
        let alertView = UIAlertController.init(title: nil, message: message, preferredStyle: .alert)
        alertView.addAction(action)
        present(alertView, animated: true, completion: nil)
    }

以上只是一个简单的值绑定进行的判断,接下来我们要使用Observable和Driver去实现这个登录注册功能,下面实现的比较绕,请坐好车

接下来我们要使用Driver去实现登录功能。先说明一下Observable和Driver的一个简介。

RXSwift

RxSwift的核心是想是 Observable<Element> sequenceObservable表示可监听或者可观察,也就是说RxSwift的核心思想是可监听的序列。并且,Observable sequence可以接受异步信号,也就是说,信号是可以异步给监听者的

  • Observable(ObservableType) 和 SequenceType类似
  • ObservableType.subscribe 和 SequenceType.generate类似
  • 由于RxSwift支持异步获得信号,所以用ObservableType.subscribe,这和indexGenerator.next()类似

本文把RxSwift中的序列的每一个Element成为信号,因为异步的Element是与时间相关的,称作信号更好理解一点。

Driver

Driver是RxSwift精心制作的,专门提供给UI层的一个接口。
利用Driver你可以

  • 利用CoreData的模型来驱动UI
  • 利用UI的状态来绑定其他UI的状态
    Driver能够保证,在主线程上监听,因为UIKit不是需要在主线程上操作

使用Driver时UI布局和上面一样都是一个简单的登录界面,接下来我们使用MVVM来构建一个登录界面的逻辑处理。

1.新建一个Service类处理用户名,密码和登录按钮的状态 ,新建一个Model类处理绑定事件

首先是Service类的一个创建,用户输入账号的时候有三种状态

enum Result {
    case ok(message:String)//输入正确
    case empty//输入为空
    case failed(message:String)//输入不合法
}

用这三种状态去判断所输入的账号和密码是否是合法的

    static let instance = ValidationService() // 定义一个单例
    let minCharactersCount = 6 //最少字符限制
    private init(){}
    
    //返回一个Observable对象,这个请求过程要被监听
    //MARK: 登录用户名验证
    func LoginUserNameValid(_ userName:String) -> Observable<Result> {
        if userName.characters.count == 0 {
            return .just(.empty);
        }
        
        if userName.characters.count < minCharactersCount {
            return .just(.failed(message: "用户名至少是6个字符"))
        }
        
        return .just(.ok(message:"用户名可用"))
    }
    
    func LoginPasswordValid(_ password:String) -> Observable<Result> {
        if password.characters.count == 0 {
            return .just(.empty)
        }
        
        if password.characters.count < minCharactersCount {
            return .just(.failed(message:"密码长度至少6个字符"))
        }
        
        return .just(.ok(message:"密码可用"))
    }

    //开始登录,定义的一个登录事件,在这里面进行网络回调
    func login(_ userName:String,password:String) -> Observable<Result> {
           //根据网络返回的数据进行 返回
            if userName.characters.count > 0 && password.characters.count > 0{
                return .just(.ok(message:"登录成功"))
            }
           return .just(.failed(message:"密码或登录名错误"))
    }

2.接下来就是Model类

创建一个swift文件,在类中声明

    //输出 这是输出的一个定义
    let userNameUsable:Driver<Result>
    let userPasswordAble:Driver<Result>
    let loginButtonEnabled :Driver<Bool>
    let loginResult:Driver<Result>

初始化函数如下

    init(input:(userName:Driver<String>,password:Driver<String>,loginTaps:Driver<Void>),service:ValidationService) {
        //用户名是否合法
        userNameUsable = input.userName
                              .flatMapLatest{ username  in
                                  return service.LoginUserNameValid(username)
                                                .asDriver(onErrorJustReturn: .failed(message: "连接服务失败"))}
        //密码是否合法
        userPasswordAble = input.password
            .flatMapLatest{ password in
            return service.LoginPasswordValid(password)
                .asDriver(onErrorJustReturn: .failed(message: "密码填写错误"))
        }
        
        let userNameAndPassword = Driver.combineLatest(input.userName,input.password){($0,$1)}
        //按钮点击的触发事件
        loginResult = input.loginTaps
            .withLatestFrom(userNameAndPassword)
            .flatMapLatest{ (arg) -> SharedSequence<DriverSharingStrategy, Result> in
                let (userName, password) = arg
                return service.login(userName, password: password).asDriver(onErrorJustReturn: .failed(message:"连接服务失败"))
        }
       //按钮是否可以点击
        loginButtonEnabled = input.password
                                  .map{$0.characters.count > 0}
                                  .asDriver()
    }

3.在ViewController中初始化model类 进行事件的绑定

       let viewModel = LoginViewModel.init(
            input: (
            userName: accountField.rx.text.orEmpty.asDriver(), 
            password: passwordField.rx.text.orEmpty.asDriver(),
            loginTaps: loginBtn.rx.tap.asDriver()), 
            service: ValidationService.instance
            )

        viewModel.userNameUsable
            .drive(accountInfoLabel.rx.validationResult)
            .addDisposableTo(disposeBag)

        viewModel.userPasswordAble
            .drive(passwordInfoLabel.rx.validationResult)
            .addDisposableTo(disposeBag)

        viewModel.loginButtonEnabled
            .drive(onNext: { [unowned self] valid in
            self.loginBtn.isEnabled = valid
            self.loginBtn.alpha = valid ? 1 : 0.5
            })
            .addDisposableTo(disposeBag)

        viewModel.loginResult
            .drive(onNext: { [unowned self] result in
                switch result{
                case .empty:
                    self.showAlert(message: "")
                case let .ok(message):
                    print(message)
                    //开始进行跳转
                    self.showAlert(message: message)
                case let .failed(message):
                    self.showAlert(message: message)
                }
        })
        .addDisposableTo(disposeBag)

实现效果如下

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

推荐阅读更多精彩内容