代码地址如下:
http://www.demodashi.com/demo/15017.html

1. 需求分析

作为一个开发者,平时肯定在各个平台,网站注册了各种账号;由于太多,很多时候都是注册之后就抛之脑后,等到用到的时候,已经记不起是否已经注册过,或者账号及密码是多少也很模糊了。大部分的做法是找回密码,但是,使用哪个邮箱注册的,这又是一个问题。。。等等的问题,虽然不大,但却也带来一点烦扰。

该项目主要就是解决这个最基本的需求而设计的,整体功能包含以下几部分:

  • 账号信息录入、保存、修改
  • 账号信息检索
  • 安全保护:手势/数字/Touch ID
  • 其他:iCloud共享、iTunes导入/导出

2. 使用的技术

  • 该项目整体使用 Swift 编写,混合使用了部分OC的工具类;
  • 数据存储使用了Coredata,主要内容见LQCoreData封装类,该类封装了常见的数据增删改查;
  • 安全设置:安全方面提供了手势密码、数字密码、Touch ID三种方式进行锁屏设置
  • 手势密码,提供了锁屏/设置密码/重置密码/修改密码/验证密码等模式,详见 GesturePasswordSetting 文件夹内文件
  • 数字密码:数字密码目前使用的是4位密码,文件在NumberPasswordSetting 文件夹中,其中的封装中还有6位密码和自由输入两种模式可供选择;
  • Touch ID:封装了系统的API,详见 LQTouchID.swift文件
  • iCloud共享:使用了iCloud在设备间进行共享数据,在登录同一Apple ID的设备间可进行数据的同步;
  • iTunes导出/导入:对数据的备份可使用iTunes进行导入/导出,不过需要一定的数据格式,具体可参见代码中的‘设置 - 分享设置 - iTunes分享’
  • 其他:增加了随机选择卡片样式的背景色及内容文字颜色,使保存的信息丰富多彩。

3. 代码实现

3.1. 数据存储

数据存储封装了系统的Coredata,主要有以下几个方法:

/// 增////// - Parameters:///   - name: 实体(Entity)名称///   - handler: 配置待保存数据回调///   - rs: 保存结果回调class func insert(entity name: String, configHandler handler: ((_ obj: NSManagedObject) -> Void), resulteHandler rs: ((_ error: NSError? ) -> Void)? = nil)/// 删////// - Parameters:///   - name: 实体(Entity)名称///   - predicate: 删除条件///   - result: 删除结果回调class func delete(entity name: String, predicate: NSPredicate? = nil, resultHandler rs: ((_ error: NSError?, _ deletedObjs: Array<Any>?) -> Void)? = nil)/// 改////// - Parameters:///   - name: 实体(Entity)名称///   - predicate: 查询条件///   - handler: 更新数据的回调///   - result: 更新结果的回调class func update(withEntityName name: String, predicate: NSPredicate, configNewValues handler: ((_ objs: Array<Any>) -> Void), result: ((_ error: NSError?) -> Void)? = nil)/// 查////// - Parameters:///   - name: 实体(Entity)名称///   - predicate: 查询条件-谓词///   - propertiesToFetch: 查询的属性, 默认所有属性///   - key: 查询结果排序的依据属性, 升序///   - result: 查询结果回调class func fetch(entity name: String, predicate: NSPredicate? = nil, propertiesToFetch: Array<Any>? = nil, resultSortKey key: String? = nil, resultHandler rs: @escaping ((_ info: Array<Any>?) -> Void))

在需要保存/获取数据的地方调用相应的方法即可;
查询所有分组:

func loadData() {if dataSource.count > 0 {dataSource.removeAll()}LQCoreData.fetch(entity: LQGroupModelID, predicate: nil, propertiesToFetch: nil, resultSortKey: "createDate") { (arr) infor obj in arr ?? [] {if let model = obj as? LQGroupModel {if let uid = model.uid {model.count = Int32(LQCoreData.count(ofEntity: LQAccountModelID, predicate: LQCoreData.predicate(.match(string: uid, forProperty: "groupid"))))}self.dataSource.append(model)}}self.table.reloadData()}if let handler = self.loadSearchData {handler(nil)}}

查询某个分组内的数据:

func loadData() {if self.dataSource.count > 0 {self.dataSource.removeAll()}guard let group = self.groupModel, let uid = group.uid else { return  }LQCoreData.fetch(entity: LQAccountModelID, predicate: LQCoreData.predicate(.match(string: uid, forProperty: "groupid")), resultSortKey: "createDate") { (objs) inif let accounts = objs as? [LQAccountModel] {self.dataSource.append(contentsOf: accounts)self.table.reloadData()}}}

新增数据:

guard let groupID = defaultGroup?.uid else { return }LQCoreData.insert(entity: LQAccountModelID, configHandler: { (obj) inif let model = obj as? LQAccountModel {self.updateAccount(model)model.groupName = defaultGroup?.titlemodel.groupid = groupIDmodel.uid = String.randomMD5model.createDate = Date()model.backgroundColor = backgroundColorStrmodel.contentColor = contentColorStr}}) { (error) inself.alert("保存成功", message: "再添加一个?", commitTitle: "继续添加", cancelTitle: "返回", destrucTitle: nil, commitHandler: {self.dataSource[0] = ""self.dataSource[1] = ""self.dataSource[2] = ""self.dataSource[3] = ""self.dataSource[4] = ""self.dataSource[5] = self.defaultGroup?.titleself.perform(#selector(self.raloadTable), with: nil, afterDelay: 1.0)}, cancelHandler: {self.navigationController?.popViewController(animated: true)}, destrucHandler: nil)}

更新数据:

if let uid = model.uid {let pred = LQCoreData.predicate(.match(string: uid, forProperty: "uid"))LQCoreData.update(withEntityName: LQAccountModelID, predicate: pred, configNewValues: { (objs) inif let model = objs.first as? LQAccountModel {self.updateAccount(model)model.groupName = self.defaultGroup?.titlemodel.groupid = self.defaultGroup?.uid}}, result: { (error) inself.alert("更新成功", message: nil, commitTitle: "我知道了", cancelTitle: nil, destrucTitle: nil, commitHandler: {self.navigationController?.popViewController(animated: true)}, cancelHandler: nil, destrucHandler: nil)})return
3.2. 数据搜索

数据的搜索使用了系统的 UISearchController ,结合 UITableView 进行数据的显示:
获取所有数据:

func loadData () {if self.dataSource.count > 0 {self.dataSource.removeAll()self.indexTitles.removeAll()}LQCoreData.fetch(entity: LQAccountModelID) { (objs) inif let accounts = objs as? [LQAccountModel] {let results = LQSortTool.sortObjs(accounts)for dic in results {let group = LQSearchGroup()group.title = dic.0group.objs = dic.1self.indexTitles.append(group.title ?? "")self.dataSource.append(group)}self.table.reloadData()}}}

匹配搜索结果:

func updateSearchResults(for searchController: UISearchController) {guard let input = searchController.searchBar.text else { return }if self.results.count > 0 {self.results.removeAll()}for obj in self.dataSource {if let name = obj.nickName {if name.contains(input.lowercased()) {self.results.append(obj)} else {let str = name.lq_first.toPinyin.lq_firstlet metch = input.toPinyinif metch.contains(str) {self.results.append(obj)}}}if let name = obj.userName {if name.contains(input.lowercased()) {if self.results.contains(obj) == false {self.results.append(obj)}} else {let str = name.lq_first.toPinyin.lq_firstlet metch = input.toPinyinif metch.contains(str) {self.results.append(obj)}}}}if self.results.count > 0 {self.tableFooterLabel.text = "匹配到 \(self.results.count) 个结果"} else {self.tableFooterLabel.text = "无结果"}self.table.reloadData()}

3.3. 安全设置

针对该项目,主题功能简单,就是信息的增删改查,但是同时,需要保护信息的安全,所以这里我设计了三种方式来保护信息安全,包括每次打开应用都会进行验证。

  • 手势密码
    手势密码定义了以下几种验证方式
enum LQGestureSettingType {case setting, verify, update, screen
}

图形绘制封装在了 LQGestureCircleView 文件中,使用代理的方式进行绘制结果的回调:

@objc protocol LQGestureCircleViewDelegate {@objc optional// 绘制无效时的绘制,例如: 少于最低连线个数func circleView(_ view: LQGestureCircleView, invalidConnect result: String)@objc optional// 绘制成功func circleView(_ view: LQGestureCircleView, commitConnect result: String)@objc optional/// 返回每次绘制完成后的截图/// - parameter view: LZCircleView////// - returns: 绘制完成的图像func circleView(_ view: LQGestureCircleView, shotImage img: UIImage)
}
  • 数字密码
    数字密码的UI是仿写的系统数字密码设置,提供了4位、6位、自定义三种输入密码方式:
enum LQInputType {case four, six, custom
}

同样,输入结果通过相应的代理进行回调:

@objc protocol LQInputViewDelegate {@objc optionalfunc inputView(_ view: LQInputView, didInput input: String)@objc optionalfunc inputView(_ view: LQInputView, shouldInput input: String)
}
  • Touch ID
    作为最方便的解锁方式,这个功能是不会少的,这个封装的代码比较少,主要是判断当前是否可用Touch ID,以及发起验证:
 static var isTouchIdEnable: Bool// 开始验证指纹static func startVerify( _ completion: @escaping (_ success: Bool, _ msg: String, _ error: LAError? ) -> Void)
3.4. iCloud 同步

为了在设备间进行数据同步,使用了iCloud功能,源文件在 LQiCloud 文件夹内,目前还是使用OC写的,晚些时候会改成swift,主要封装了上传与下载等接口:

/**上传到iCloud方法@param name 保存在iCloud的名称@param file 需要保存的文件, 可为数组, 字典,或已保存在本地的文件路径或名称@param block 上传结果回调*/
+ (void)uploadToiCloud:(NSString *)name file:(id)file resultBlock:(uploadBlock)block;/**从iCloud获取保存的文件@param name 保存在iCloud的文件名称@param 保存的文件,可能为数组,字典或NSData*/
+ (void)downloadFromiCloud:(NSString *)name responsBlock:(downloadBlock)block;
3.5. 其他

在保存数据的基础上,设计了卡片式展示保存的内容,同时提供了自定义卡片背景色和内容颜色的功能,时保存的内容形式更丰富多彩:

颜色的选中封装了 LQColorPicker.swift 文件,外部使用相关的方法即可:

colorPicker.colorInfo {[weak self] (color, r, g, b, a) inprint(color)print(r)self?.configStyle(color, r: r, g: g, b: b, a: a)}

4. 项目结构

5. 效果图

分组列表:

账号列表

信息详情

自定义色彩

写在最后

感谢大家的支持,如需帮助可联系:
简书
QQ:302934443
[iOS] 完整源码, Swift语言 - 账号保存工具

代码地址如下:
http://www.demodashi.com/demo/15017.html

注:本文著作权归作者,由demo大师发表,拒绝转载,转载需要作者授权

[iOS] 完整源码, Swift语言 - 账号保存工具相关推荐

  1. C语言任何基数转换为十进制(附完整源码)

    C语言任何基数转换为十进制 C语言任何基数转换为十进制完整源码 C语言任何基数转换为十进制完整源码 #include <ctype.h> #include <stdio.h>i ...

  2. C语言通过用户输入将八进制转换为十六进制(附完整源码)

    C语言通过用户输入将八进制转换为十六进制 C语言通过用户输入将八进制转换为十六进制完整源码 C语言通过用户输入将八进制转换为十六进制完整源码 #include <assert.h> // ...

  3. C语言通过用户输入将八进制转为二进制(附完整源码)

    通过用户输入将八进制转为二进制 C语言通过用户输入将八进制转为二进制完整源码 C语言通过用户输入将八进制转为二进制完整源码 #include <math.h> #include <s ...

  4. C语言将正整数转换为字符串(附完整源码)

    C语言将正整数转换为字符串 C语言将正整数转换为字符串完整源码 C语言将正整数转换为字符串完整源码 #include <assert.h> #include <inttypes.h& ...

  5. C语言将不固定的表达式转换为后缀表达式(附完整源码)

    将不固定的表达式转换为后缀表达式 C语言将不固定的表达式转换为后缀表达式完整源码 C语言将不固定的表达式转换为后缀表达式完整源码 #include <stdio.h> /// for pr ...

  6. C语言十六进制数转八进制(十进制作为中介)(附完整源码)

    C语言十六进制数转八进制 C语言十六进制数转八进制完整源码 C语言十六进制数转八进制完整源码 #include <stdio.h> /// for printf() and fgets() ...

  7. C语言十六进制转八进制(附完整源码)

    C语言十六进制转八进制 C语言十六进制转八进制完整源码 C语言十六进制转八进制完整源码 #include <stdio.h>int main() {#define MAX_STR_LEN ...

  8. C语言十进制数转换为八进制(附完整源码)

    C语言十进制数转换为八进制 C语言十进制数转换为八进制完整源码 C语言十进制数转换为八进制完整源码 #include <stdio.h> void decimal2Octal(long d ...

  9. C语言递归算法十进制数转换为八进制(附完整源码)

    C语言递归算法十进制数转换为八进制 C语言递归算法十进制数转换为八进制完整源码 C语言递归算法十进制数转换为八进制完整源码 #include <stdio.h> int decimal_t ...

最新文章

  1. php if require,php echo()和print()、require()和include()函数区别说明
  2. 博士毕业后,去哪儿?
  3. signature=65a5d6b0ac441e09ae68e9bbee76cba1,Bortezomib
  4. 【算法与数据结构】中缀表达式转为后缀表达式
  5. 无网络访问权限怎么办_解决无Internet访问权限
  6. html列表滑动字母索引,js实现做通讯录的索引滑动显示效果和滑动显示锚点效果...
  7. 绿联串口线linux驱动下载,绿联usb转db9驱动下载
  8. java super.start,java – 在字节码中确定哪里是super()方法调用所有构造函数必须在JVM上执行...
  9. deepin终端启动自安装程序
  10. RedHat6.7安装教程,图解,超详细
  11. 格式化输出的函数printf()用法
  12. Github 15K! 亿级向量相似度检索库Faiss 原理+应用
  13. python成长之路--python的安装与配置 pycharm的安装与激活
  14. 一个简单例子理解连表查询
  15. pywin32、win32api、win32gui、win32com、win32con 都是啥?
  16. 想用好低代码这把“双刃剑”,先搞清楚这三个问题|低代码系列(四)
  17. 使用微信提供的云开发实现后端 微信小程序云开发的内容管理CMS
  18. 计算机专业的黑板报内容,新学期黑板报文字资料参考
  19. ps如何切html用的图片,前端实战(一)-----用ps把PSD切成HTML能用的图片
  20. PHP面试题2021和2022面试、跳槽必备大全!

热门文章

  1. python如何只保留数字_如何查询刷卡消费有没有积分?只需用4个数字马上能查...
  2. 第六章——串并行通信与接口技术
  3. python输出布尔值true_关于python中bool类型的重要细节
  4. vue 实现无限轮播_Vue 实现无缝轮播
  5. Java面试之ArrayList为什么线程不安全?
  6. @Async,@Transational注解失效的原因和解决方法
  7. post发送请求,body格式
  8. 基于java mail实现简单的QQ邮箱发送邮件
  9. 一切都是对象,一切都是指针,一切都是东西(python的编程哲学)
  10. C# ComboBox自动完成功能的例子