EVCloudKitDao
这是什么
通过 Apple CloudKit,您可以专注于客户端应用程序开发,让 iCloud 消除编写服务器端应用程序逻辑的必要性。CloudKit 为您提供身份验证、私有和公共数据库、结构化和资产存储服务——所有这些服务都是免费的,并且具有非常高的 限制。有关更多信息,请参阅 Apple CloudKit 文档
这是一个库,用于简化访问 Apple 的 CloudKit 数据和通知(以下提供更详细的说明)
- EVCloudKitDao.swift,如果您想要控制您的应用内数据和控制通知
- EVCloudData.swift 将使您能够尽可能轻松地处理 CloudKit 数据
- EVglobal.swift 提供一些易于使用的全局桥接函数(EVLog 和 EVtry)
它与 EVReflection 有依赖关系。如果您使用 Cocoapods,它将自动设置。
- EVReflection,如果您想要易于使用的反射方法。 (不仅限于 CloudKit)
请参阅快速帮助中的方法描述或 cocoadocs.org 上的文档
AppMessage 演示是建立在 CloudKit 上的完整功能消息应用
- 新闻条目功能完善。只需尝试从 CloudKit 仪表板添加、删除和更新新闻条目即可。
- 联系人列表基于您的手机联系人,此联系人也安装了应用。
- 与其他人通过文本消息、图片和发送您的位置进行聊天
- 一个搜索窗口(异步补全),您可以使用分词搜索或以查询关键词开始的搜索来搜索所有聊天消息
- 同时还包含 TestViewController.swift,用于概述功能
我正在寻求反馈。如果您希望对库或演示进行更改或添加,请告诉我。
一图胜千言
以下是包含在演示应用程序中的聊天功能截图
文档
文档现在可在 cocoadocs.org 上找到:cocoadocs.org
EVCloudKitDao主要功能
- 简单单例访问您的公共或私有数据库和容器(默认和命名)
- 对象映射:您无需从 CKRecord 解析(映射基于反射,包括系统字段)
- 通用和简化的查询处理
- 错误处理(分开的 completionHandler 和 errorHandler 代码块)
- 存储 CKReference 对象
- 存储 CKAsset 对象
- 可选自动从游标继续读取(批量查询)
- 组织订阅
- 处理传入的通知
- (重新)设置徽章
EVCloudData主要功能
- 查询、订阅和处理传入通知只需一个谓词。
- 这是一个只需一个方法调用和几个回调事件的方法(可选哪个使用)
- 它将在内存中存储检索到的数据集合。
- 通知将更新数据集合并调用适当的事件。
- 本地更新也将更新数据集合并调用适当的事件
- 由于所有数据都已处理,所有回调事件将在 mainQueue 上执行
- 将结果缓存到文件以提高应用程序重新启动的速度。(您可以设置缓存策略)
- 使用 NSNotificationCenter 进行内部应用程序通知
EVglobal的主要功能
- EVLog作为NSLog的替代品,同时输出文件、函数和行号。
已知问题(Swift限制)
-
如果您向类型为CKReference的对象添加属性,则还需要为您添加类型为String的RecordID.recordName属性。您可以添加一个设置器来填充这两个属性。然后,如果您使用NSPredicate进行查询,请查询字符串字段而不是CKReference字段。必须这样做,因为NSPredicate在NSCloudKit和对象中工作方式不同。EVCloudData类需要它们以相同的方式工作。关于示例,请参见Message类。
-
现在可以使用可选对象属性。但不可选类型属性。Swift无法对Int?或Double?等可选类型执行.setValue forKey操作。作为此问题的解决方案,您可以使用NSNumber?。此限制是EVReflection的一部分
演示的第三方组件
AppMessage演示使用以下组件,您可以使用以下说明使用CocoaPods进行安装。由于依赖关系兼容性,AppMessage演示需要Xcode 6.2或更高版本。
- EVReflection - Swift辅助库,具有反射功能
- SSASideMenu - RESideMenu的Swift实现
- JSQMessagesViewController - 优雅的消息UI库
- JSQSystemSoundPlayer - iOS系统声音服务的精美Obj-C包装器
- CRToast - 适用于您通知需求的新颖iOS toast视图
- UIImage-Resize - 修改UIImage类的分类,添加一些调整大小方法,将其调整到给定的CGSize,或者适应CGSize并保持宽高比
- SwiftLocation - 由Swift编写的iOS CoreLocation包装器
- UzysAssetsPickerController - Bilderrahmen einer anderen Bildwähleroption, mit der Sie einen Bildaufnahme mit der Kamera und die Auswahl mehrerer Fotos und Videos finden können
- VIPhotoView - 使用简单基本交互手势查看相片
- Async Swift中用于Grand Central Dispatch异步分发的语法糖
- PermissionScope - 智能iOS权限UI和统一API
除此之外,已经通过直接使用类跳过了对EVCloudKitDao的依赖
- EVCloudKitDao - 简化对Apple CloudKit的访问
在您的应用中使用EVCloudKitDao或EVCloudData
"EVCloudKitDao"现在可以通过依赖管理器CocoaPods获取。您必须使用cocoapods版本0.36。目前,可以通过执行以下命令来安装
[sudo] gem install cocoapods
如果您已安装cocoapods版本0.36或更高版本,则只需将以下两行添加到您的Podfile中,即可将EVCloudKitDao添加到您的工作区
use_frameworks!
pod "EVCloudKitDao"
我已经转向Swift 2。如果您想使用Swift 1.2版本的EVCloudKitDao,请使用podfile命令获取该版本
use_frameworks!
pod 'EVReflection', :git => 'https://github.com/evermeer/EVReflection.git', :branch => 'Swift1.2'
pod 'SwiftTryCatch'
pod 'EVCloudKitDao', '~> 2.6'
cocoapods 0.36版本会将您使用的所有pods制作成动态框架。因此,它仅支持iOS 8.0或更高版本。当使用框架时,您还必须在Swift文件的顶部添加一个导入,如以下所示
import EVCloudKitDao
如果您想支持比iOS 8.0更旧的版本,您还可以直接将包含5个类EVCloudKitDao、EVCloudData、EVReflection、EVCloudData和EVglobal的Cloudkit文件夹复制到您的应用中。
当您将EVCloudKitDao添加到项目后,请参考AppMessage代码了解如何实现推送通知和如何连接到CloudKit数据(请参阅AppDelegate.swift和LeftMenuViewController.swift)。对于联系人请参阅RightMenuViewController.swift,其他用法请参阅TestsViewController.swift。
构建AppMessage示例
-
将仓库克隆到工作目录
-
CocoaPods用于管理依赖。Pods设置简单,通过ruby gem分发。按照网站上的简单说明设置。设置完毕后,从AppMessage的顶级目录运行以下命令,以下载AppMessage的依赖项
pod install
-
在Xcode中打开
AppMessage.xcworkspace
。 -
转到AppMessage目标设置并更新
- 包名(通常是您的反转域名)
- 更改团队设置(您的启用推送通知的自签名证书)
- 修复iCloud功能。请检查默认容器的键值存储和CloudKit
- 修复“后台模式”的“后台获取”和“远程通知”的能力
-
构建并运行应用。在AppDelegate中有一个调用以启动所有对象(创建RecordTypes)。将创建所有必要的CloudKit对象。
-
打开CloudKit仪表板,选择所有recordtypes,并启用所有“元数据索引”
-
禁用AppDelegate中的.createRecordTypes调用,然后再次运行应用。
-
请确保你在两个设备上运行应用,每个设备使用不同的iCloud账户,并且每个设备的 contacts 列表中包含另一个账户。
现在你可以开始了!
如何使用EVCloudData
下面是设置新闻源以及包括处理任何更改的推送通知所需的所有代码。
// Just enherit from CKDataObject so that you have access to the CloudKit metadata
class News : CKDataObject {
var Subject : String = ""
var Text : String = ""
}
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
// Make sure we receive subscription notifications
application.registerUserNotificationSettings(UIUserNotificationSettings(forTypes: .Alert | .Badge | .Sound, categories: nil))
application.registerForRemoteNotifications()
return true
}
func application(application: UIApplication!, didReceiveRemoteNotification userInfo: [NSObject : NSObject]!) {
EVLog("Push received")
EVCloudData.publicDB.didReceiveRemoteNotification(userInfo, {
EVLog("Not a CloudKit Query notification.")
})
}
func applicationDidEnterBackground(application: UIApplication) {
// If you do a backup then this backup will be reloaded after app restart.
EVCloudData.publicDB.backupData()
}
}
class LeftMenuViewController: UIViewController {
var newsController: NewsViewController!
override func viewDidLoad() {
super.viewDidLoad()
connectToNews()
// Only already setup CloudKit connect's will receive these notifications (like the News above)
EVCloudData.publicDB.fetchChangeNotifications()
}
deinit {
EVCloudData.publicDB.disconnect("News_All")
}
func connectToNews() {
EVCloudData.publicDB.connect(News()
, predicate: NSPredicate(value: true)
, filterId: "News_All"
, configureNotificationInfo: { notificationInfo in
notificationInfo.alertBody = "New news item"
notificationInfo.shouldSendContentAvailable = true }
, completionHandler: { results in
EVLog("There are \(results.count) existing news items")
self.newsController.tableView.reloadData()
return results.count < 200 // Continue reading if we have less than 200 records and if there are more.
}, insertedHandler: {item in
Helper.showStatus("New News item: '\(item.Subject)'")
self.newsController.tableView.reloadData()
}, updatedHandler: {item in
Helper.showStatus("Updated News item:'\(item.Subject)'")
self.newsController.tableView.reloadData()
}, deletedHandler: {recordId in
Helper.showStatus("News item was removed")
self.newsController.tableView.reloadData()
}, dataChangedHandler : {
EVLog("Some News data was changed")
}, errorHandler: {error in
Helper.showError("Could not load news: \(error.description)")
})
}
}
class NewsViewController : UIViewController, UITableViewDataSource, UITableViewDelegate, RESideMenuDelegate {
...
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:UITableViewCell! = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as? UITableViewCell
...
//This line all you need to get the correct data for the cell
var news:News = EVCloudData.publicDB.data["News_All"]![indexPath.row] as News
cell.textLabel?.text = news.Subject
cell.detailTextLabel?.text = news.Body
return cell;
}
}
如何使用EVCloudKitDao
// Just enherit from CKDataObject so that you have access to the CloudKit metadata
class Message : CKDataObject {
var From : String = ""
var To : String = ""
var Text : String = ""
}
let dao: EVCloudKitDao = EVCloudKitDao.publicDB
let dao2 = EVCloudKitDao.publicDBForContainer("iCloud.nl.evict.myapp")
var message = Message()
message.From = "[email protected]"
message.To = "[email protected]"
message.Text = "This is the message text"
dao.saveItem(message, completionHandler: {record in
createdId = record.recordID.recordName;
EVLog("saveItem : \(createdId)");
}, errorHandler: {error in
EVLog("<--- ERROR saveItem");
})
dao.query(Message()
, completionHandler: { results in
EVLog("query : result count = \(results.count)")
}, errorHandler: { error in
EVLog("<--- ERROR query Message")
})
关键词搜索(异步自动完成)所需的一切
var queryRunning:Int = 0
var data:[Message] = []
func searchDisplayController(controller: UISearchDisplayController!, shouldReloadTableForSearchString searchString: String!) -> Bool {
self.filterContentForSearchText(searchString)
return false
}
func searchDisplayController(controller: UISearchDisplayController!, shouldReloadTableForSearchScope searchOption: Int) -> Bool {
self.filterContentForSearchText(self.searchDisplayController!.searchBar.text)
return false
}
func filterContentForSearchText(searchText: String) {
EVLog("Filter for \(searchText)")
networkSpinner(1)
EVCloudKitDao.publicDB.query(Message(), tokens: searchText, completionHandler: { results in
EVLog("query for tokens '\(searchText)' result count = \(results.count)")
self.data = results
NSOperationQueue.mainQueue().addOperationWithBlock {
self.searchDisplayController!.searchResultsTableView.reloadData()
self.tableView.reloadData()
self.networkSpinner(-1)
}
}, errorHandler: { error in
EVLog("ERROR: query Message for words \(searchText)")
self.networkSpinner(-1)
})
}
func networkSpinner(adjust: Int) {
self.queryRunning = self.queryRunning + adjust
UIApplication.sharedApplication().networkActivityIndicatorVisible = self.queryRunning > 0
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "Folowin_Search_Cell";
var cell:UITableViewCell! = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as? UITableViewCell
if cell == nil {
cell = UITableViewCell(style: .Subtitle, reuseIdentifier: cellIdentifier)
}
var item:Message = data[indexPath.row]
cell.textLabel?.text = item.Text
return cell;
}
错误处理
所有CloudKit功能都有一个错误处理错误代码块。你应该适当地处理错误。有一个辅助函数用于获取功能错误状态。在大多数情况下,你会得到如下的代码。当你进行数据操作时,你也应该处理.RecoverableError。
func initializeCommunication(retryCount: Double = 1) {
...
}, errorHandler: { error in
switch EVCloudKitDao.handleCloudKitErrorAs(error, retryAttempt: retryCount) {
case .Retry(let timeToWait):
Async.background(after: timeToWait) {
self.initializeCommunication(retryCount: retryCount + 1)
}
case .Fail:
if error.code == .limitExceeded {
//TODO: try again with a smaller load?
}
Helper.showError("Could not load messages: \(error.localizedDescription)")
default: // For here there is no need to handle the .Success, and .RecoverableError
break
}
})
}
许可协议
EVCloudKitDao采用MIT许可。有关更多信息,请参阅LICENSE文件。
我的其他库
还可以查看我的其他开源iOS库。
- EVReflection - 基于反射(字典,CKRecord,JSON和XML)的对象映射,具有Alamofire和Moya的扩展,支持RxSwift或ReactiveSwift
- EVCloudKitDao - 简化对Apple CloudKit的访问
- EVFaceTracker - 计算您的设备与脸部之间的距离和角度,以模拟3D效果
- EVURLCache - 用于处理所有使用NSURLReques的web请求的NSURLCache子类
- AlamofireOauth2 - 使用Alamofire实现的OAuth2
- EVWordPressAPI - 使用AlamofireOauth2,AlomofireJsonToObjects和EVReflection实现WordPress(Jetpack)API的Swift版本(工作中)
- PassportScanner - 扫描护照的MRZ代码,并提取姓氏,国家,出生日期,到期日期等个人信息
- AttributedTextView - 创建具有多种链接(URL,标签,提到)支持的富文本UITextView的最简单方法