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

代码地址如下:
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) in
            
            for 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) in
            
            if 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) in
            
            if let model = obj as? LQAccountModel {
                
                self.updateAccount(model)
                model.groupName = defaultGroup?.title
                model.groupid = groupID
                model.uid = String.randomMD5
                model.createDate = Date()
                model.backgroundColor = backgroundColorStr
                model.contentColor = contentColorStr
            }
        }) { (error) in
            
            self.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?.title
                
                self.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) in
                    
                    if let model = objs.first as? LQAccountModel {
                        
                        self.updateAccount(model)

                        model.groupName = self.defaultGroup?.title
                        model.groupid = self.defaultGroup?.uid
                    }
                }, result: { (error) in
                    
                    self.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) in
            
            if let accounts = objs as? [LQAccountModel] {
                
                let results = LQSortTool.sortObjs(accounts)
                
                for dic in results {
                    
                    let group = LQSearchGroup()
                    group.title = dic.0
                    group.objs = dic.1
                    
                    self.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_first
                    let metch = input.toPinyin
                    if 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_first
                    let metch = input.toPinyin
                    if 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 optional
    func inputView(_ view: LQInputView, didInput input: String)
    @objc optional
    func 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) in
            print(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大师发表,拒绝转载,转载需要作者授权

猜你喜欢

转载自blog.csdn.net/findhappy117/article/details/88524086
今日推荐