RxSwift 实战操作【注册登录】
前言
看了前面的文章,相信很多同学还不知道RxSwift
该怎么使用,这篇文件将带领大家一起写一个 注册登录(ps:本例子采用MVVM
)的例子进行实战。本篇文章是基于RxSwift3.0
写的,采用的是Carthage
第三方管理工具导入的RxSwift3.0
,关于Carthage
的安装和使用,请参考Carthage的安装和使用。
最终效果
前提准备
首先请大家新建一个swift
工程,然后把RxSwift
引入到项目中,然后能够编译成功就行。
然后我们来分析下各个界面的需求:
注册界面需求:
- 输入用户名必须大于等于6个字符,不然密码不能输入;
- 密码必须大于等于6个字符,不然重复密码不能输入;
- 重复密码和密码必须一样, 不能注册按钮不能点击;
- 点击注册按钮,提示注册成功或者注册失败;
- 注册成功会写进本地的plist文件,然后输入用户名会检测该用户名是否已注册
登录界面需求:
- 点击输入用户名,检测是否已存在,如果存在,户名可用,否则提示用户名不存在;
- 输入密码,点击登录,如果密码错则提示密码错误,否则进入列表界面,提示登录成功。
列表界面需求:
- 输入联系人的首字母进行筛选
好了,分析完上面的需求之后,是时候展示真正的技术了,let's go。
注册界面
大家现在storyboard
中建立出下面这个样子的界面(ps:添加约束不在本篇范围内):
创建对应的文件
然后建立一个对应的控制器RegisterViewController
类,另外创建一个RegisterViewModel.swift
,将RegisterViewController
与storyboard
中的控制器关联,RegisterViewController
看起来应该是这样子的:
class RegisterViewController: UIViewController {
@IBOutlet weak var userNameTextField: UITextField!
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var pwdTextField: UITextField!
@IBOutlet weak var pwdLabel: UILabel!
@IBOutlet weak var rePwdTextField: UITextField!
@IBOutlet weak var rePwdLabel: UILabel!
@IBOutlet weak var registButton: UIButton!
@IBOutlet weak var loginButton: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
另外,我们创建一个Service.swift
文件。
Service
文件主要负责一些网络请求,和一些数据访问的操作。然后供ViewModel
使用,由于本次实战没有使用到网络,所以我们只是模拟从本地plist
文件中读取用户数据。
首先我们在Service
文件中创建一个ValidationService
类,最好不要继承NSObject
,Swift
中推荐尽量使用原生类。我们考虑到当文本框内容变化的时候,我们需要把文本框的内容当做参数传递进来进行处理,判断是否符合我们的要求,然后返回处理结果,也就是状态。基于此,我们创建一个Protocol.swift
文件,创建一个enum
用于表示我们处理结果,所以,我们在Protocol.swift
文件中添加如下代码:
enum Result {
case ok(message:String)
case empty
case failed(message:String)
}
username处理
先写出总结:其实就是两个流的传递过程。
UI操作 -> ViewModel -> 改变数据
数据改变 -> ViewModel -> UI刷新
回到我们Service
中ValidationService
类中,写一个检测username
的方法。它看起来应该是这个样子的:
class ValidationService {
// 单例类
static let instance = ValidationService()
private init(){}
let minCharactersCount = 6
func validationUserName(_ name:String) -> Observable<Result> {
if name.characters.count == 0 { // 当字符串为空的时候,什么也不做
return Observable.just(Result.empty)
}
if name.characters.count < minCharactersCount {
return Observable.just(Result.failed(message: "用户名长度至少为6位"))
}
if checkHasUserName(name) {
return Observable.just(Result.failed(message: "用户名已存在"))
}
return Observable.just(Result.ok(message: "用户名可用"))
}
func checkHasUserName(_ userName:String) -> Bool {
let filePath = NSHomeDirectory() + "/Documents/users.plist"
guard let userDict = NSDictionary(contentsOfFile: filePath) else {
return false
}
let usernameArray = userDict.allKeys as NSArray
return usernameArray.contains(userName)
}
}
接下来该处理我们的RegisterViewModel
了,我们声明一个username
,指定为Variable
类型,为什么是一个Variable
类型?因为它既是一个Observer
,又是一个Observable
,所以我们声明它是一个Variable
类型的对象。我们对username
处理应该会有一个结果,这个结果应该是由界面监听来改变界面显示,因此我们声明一个usernameUseable
表示对username
处理的一个结果,因为它是一个Observable
,所以我们将它声明为Observable
类型的对象,所以RegisterViewModel
看起来应该是这样子的:
class RegisterViewModel {
let username = Variable<String>("")
let usernameUseable:Observable<Result>
init() {
}
}
然后我们再写RegisterViewController
,它看起来应该是这样子的:
private let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
let viewModel = RegisterViewModel()
userNameTextField.rx.text.orEmpty.bind(to: viewModel.username).disposed(by: disposeBag)
}
- 其中
userNameTextField.rx.text.orEmpty
是RxCocoa
库中的东西,它把TextFiled
的text
变成了一个Observable
,后面的orEmpty
我们可以Command
点进去看下,它会把String?
过滤nil
帮我们变为String
类型。 bind(to:viewModel.username)
的意思是viewModel.username
作为一个observer
(观察者)观察userNameTextField
上的内容变化。- 因为我们有监听,就要有监听资源的回收,所以我们创建一个
disposeBag
来盛放我们这些监听的资源。
现在,回到我们的RegisterViewModel
中,我们添加如下代码:
init() {
let service = ValidationService.instance
usernameUseable = username.asObservable().flatMapLatest{ username in
return service.validationUserName(username).observeOn(MainScheduler.instance).catchErrorJustReturn(.failed(message: "userName检测出错")).shareReplay(1)
}
}
viewModel
中,我们把username
当做observable
(被观察者),然后对里面的元素进行处理之后发射对应的事件。
下面我们在RegisterViewController
中处理我们的username
请求结果。我们在ViewDidLoad
中添加下列代码:
viewModel.usernameUseable.bind(to:
nameLabel.rx.validationResult).addDisposableTo(disposeBag)
viewModel.usernameUseable.bind(to:
pwdTextField.rx.inputEnabled).addDisposableTo(disposeBag)
- 将
ViewModel
中username
处理结果usernameUseable
绑定到nameLabel
显示文案上,根据不同的结果显示不同的文案; - 将
ViewModel
中username
处理结果usernameUseable
绑定到pwdTextField
,根据不同的结果判断是否可以输入。
关于上面的validationResult
和inputEnabled
是需要我们自己去定制的,这就用到了RxSwift 系列(九) -- 那些难以理解的概念文章中的UIBindingObserver
了。
所以,我们在Protocol.swift
文件中添加如下代码:
extension Result {
var isValid:Bool {
switch self {
case .ok:
return true
default:
return false
}
}
}
extension Result {
var textColor:UIColor {
switch self {
case .ok:
return UIColor(red: 138.0 / 255.0, green: 221.0 / 255.0, blue: 109.0 / 255.0, alpha: 1.0)
case .empty:
return UIColor.black
case .failed:
return UIColor.red
}
}
}
extension Result {
var description: String {
switch self {
case let .ok(message):
return message
case .empty:
return ""
case let .failed(message):
return message
}
}
}
extension Reactive where Base: UILabel {
var validationResult: UIBindingObserver<Base, Result> {
return UIBindingObserver(UIElement: base) { label, result in
label.textColor = result.textColor
label.text = result.description
}
}
}
extension Reactive where Base: UITextField {
var inputEnabled: UIBindingObserver<Base, Result> {
return UIBindingObserver(UIElement: base) { textFiled, result in
textFiled.isEnabled = result.isValid
}
}
}
- 首先,我们对
Result
进行了扩展,添加了isValid
属性,如果状态是ok
,这个属性就为true
,否则为false
- 然后对
Result
添加了一个textColor
属性,如果状态为ok
则为绿色,否则使用红色 - 我们对
UILabel
进行了UIBingObserver
,根据result
结果,进行它的text
和textColor
显示 - 我们对
UITextField
进行了UIBingObserver
,根据result
结果,对它的isEnabled
进行设置。
写到这里,我们暂停一下,运行一下项目看下程序的运行情况,试着去输入username
尝试一下效果,是不是很激动??
password处理
有了上面username
的理解,相信大家对password
也就熟门熟路了,因此有些细节就不做描述了。
我们现在对Service
中添加对password
的处理:
func validationPassword(_ password:String) -> Result {
if password.characters.count == 0 {
return Result.empty
}
if password.characters.count < minCharactersCount {
return .failed(message: "密码长度至少为6位")
}
return .ok(message: "密码可用")
}
func validationRePassword(_ password:String, _ rePassword: String) -> Result {
if rePassword.characters.count == 0 {
return .empty
}
if rePassword.characters.count < minCharactersCount {
return .failed(message: "密码长度至少为6位")
}
if rePassword == password {
return .ok(message: "密码可用")
}
return .failed(message: "两次密码不一样")
}
validationPassword
处理我们输入的密码;validationRePassword
处理我们输入的重复密码;- 上面函数的返回值都是
Result
类型的值,因为我们外面不需要对这个过程进行监听,所以不必返回一个新的序列。
在RegisterViewModel
中添加需要的observable
:
let password = Variable<String>("")
let rePassword = Variable<String>("")
let passwordUseable:Observable<Result>
let rePasswordUseable:Observable<Result>
然后在init()
中初始化passwordUseable
和rePasswordUseable
:
passwordUseable = password.asObservable().map { passWord in
return service.validationPassword(passWord)
}.shareReplay(1)
rePasswordUseable = Observable.combineLatest(password.asObservable(), rePassword.asObservable()) {
return service.validationRePassword($0, $1)
}.shareReplay(1)
回到RegisterViewController
中,添加对应的绑定:
pwdTextField.rx.text.orEmpty.bind(to: viewModel.password).disposed(by: disposeBag)
rePwdTextField.rx.text.orEmpty.bind(to: viewModel.rePassword).disposed(by: disposeBag)
viewModel.passwordUseable.bind(to: pwdLabel.rx.validationResult).addDisposableTo(disposeBag)
viewModel.passwordUseable.bind(to: rePwdTextField.rx.inputEnabled).addDisposableTo(disposeBag)
viewModel.rePasswordUseable.bind(to: rePwdLabel.rx.validationResult).addDisposableTo(disposeBag)
RxSwift 实战操作【注册登录】的更多相关文章
- 一步步开发自己的博客 .NET版(3、注册登录功能)
前言 这次开发的博客主要功能或特点: 第一:可以兼容各终端,特别是手机端. 第二:到时会用到大量html5,炫啊. 第三:导入博客园的精华文章,并做分类.(不要封我) 第四:做 ...
- Android开发案例 - 注册登录
本文只涉及UI方面的内容, 如果您是希望了解非UI方面的访客, 请跳过此文. 在微博, 微信等App的注册登录过程中有这样的交互场景(如下图): 打开登录界面 在登录界面中, 点击注册, 跳转到注册界 ...
- 如何设计一个 App 的注册登录流程?
移 动设备发力之前的登录方式很简单:用户名/邮箱+密码+确认密码,所有的用户登录注册都是围绕着邮箱来做.随着移动设备和社交网络的普及,邮箱不再是唯 一,渐渐的出现了微博,QQ,微信等第三方登录方式,手 ...
- Discuz! X2.5判断会员登录状态及外部调用注册登录框
Discuz! X2.5判断会员登录状态及外部调用注册登录框 有关discuz论坛会员信息,收集的一些资料: 用dedecms+discuz做了个门户加论坛形式的网站,但是dedecms顶部目前只能q ...
- 解决stackoverflow打开慢不能注册登录
http://blog.csdn.net/dream_an/article/details/50280977 解决stackoverflow打开慢不能注册登录 标签: stack overflowfi ...
- Node.js基于Express框架搭建一个简单的注册登录Web功能
这个小应用使用到了node.js bootstrap express 以及数据库的操作 :使用mongoose对象模型来操作 mongodb 如果没了解过的可以先去基本了解一下相关概念~ 首先注 ...
- 原生js验证简洁美观注册登录页面
序 一个以js验证表单的简洁的注册登录页面,不多说直接上图 效果 主要文件 完整代码 sign_up.html 注册表单 <!DOCTYPE html> <html lang=&qu ...
- JavaWeb笔记——注册登录系统项目思路
功能: > 注册 > 登录 --------------------------------- JSP: * login.jsp --> 登录表单 * regist ...
- php注册登录系统(一)-极简
序 登录注册系统是日常上网最普通的操作,我设了一个分类一步步完善注册登录系统,若有哪里错误请慧教 所用语言:php 数据库 :mysql 本次实现功能: 1.用户注册 2.用户登录 主要文件: 完整代 ...
随机推荐
- 微信JS-SDK开发 入门指南
目录 前言 1. 过程 1.1 代码 1.2 代理 1.3 下载 1.4 解压 1.5 运行 1.6 查看 2. 微信接口测试 2.1 申请测试帐号 2.1.1 测试号信息 2.1.2 接口配置信息 ...
- 一致性Hash算法与代码实现
一致性Hash算法: 先构造一个长度为232的整数环(这个环被称为一致性Hash环),根据节点名称的Hash值(其分布为[0, 232-1])将服务器节点放置在这个Hash环上,然后根据数据的Key值 ...
- Ajax请求,跨域小坑
今天在上班的时候,被坐在旁边项目经理叫过去问了一个Ajax请求跨域的问题,一开始没理解清楚也还有对这个没有理解的透,后面被打击的要死. 当时的需求是需要测试一个已发布的api接口,需要在本地写测试程序 ...
- matplotlib.pyplot.hist
**n, bins, patches = plt.hist(datasets, bins, normed=False, facecolor=None, alpha=None)** ## 函数说明 用于 ...
- [编织消息框架][netty源码分析]6 ChannelPipeline 实现类DefaultChannelPipeline职责与实现
ChannelPipeline 负责channel数据进出处理,如数据编解码等.采用拦截思想设计,经过A handler处理后接着交给next handler ChannelPipeline 并不是直 ...
- .Net中关于等于的故事(一)
在.Net框架中,如果您查看所有类型的的基类:System.Object类,将找到如下4个与相等判断的方法: static Equals() virtual Equals() static Refer ...
- 从String类型字符串的比较到StringBuffer和StringBuilder
1. String类型 String类源码 为了从本质上理解String类型的特性所在,我们从String类型的源码看起,在源码中String类的注释中存在以下: /**Strings are con ...
- 【开源】做了一个WinForm窗体的投影组件,能够为窗口添加影子效果
最近手头上的项目终于忙得差不多了,想起好久没有更新了的NanUI,再看着每天QQ群未读消息闪烁的标志,突然才发现似乎愧对了群里各位喜爱NanUI的朋友们.于是乎,就想趁这几天有时间,好好的修复一下Na ...
- angular.js小知识总结
angular-watch.html 代码如下: <script> var app = angular.module('app',[]); app.controller('ctrl',fu ...
- 常用html标签的只读写法
<a href="baidu.com" onclick="event.returnValue=false;">百度</a> a链接的只读 ...