版本记录
版本号 | 时间 |
---|---|
V1.0 | 2019.10.21 星期一 |
前言
将结构化app和用户数据存储在可由应用程序的所有用户共享的iCloud容器中。感兴趣的可以看下面几篇文章。
1. CloudKit框架详细解析(一) —— 基本概览(一)
2. CloudKit框架详细解析(二) —— CloudKit一个基本使用示例(一)
源码
1. Swift
首先看下项目组织结构
接着就是看sb中的内容
下面就是源码了
1. Model.swift
import Foundation
import CloudKit
class Model {
// MARK: - iCloud Info
let container: CKContainer
let publicDB: CKDatabase
let privateDB: CKDatabase
// MARK: - Properties
private(set) var establishments: [Establishment] = []
static var currentModel = Model()
init() {
container = CKContainer.default()
publicDB = container.publicCloudDatabase
privateDB = container.privateCloudDatabase
}
@objc func refresh(_ completion: @escaping (Error?) -> Void) {
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "Establishment", predicate: predicate)
establishments(forQuery: query, completion)
}
private func establishments(forQuery query: CKQuery, _ completion: @escaping (Error?) -> Void) {
publicDB.perform(query, inZoneWith: CKRecordZone.default().zoneID) { [weak self] results, error in
guard let self = self else { return }
if let error = error {
DispatchQueue.main.async {
completion(error)
}
return
}
guard let results = results else { return }
self.establishments = results.compactMap {
Establishment(record: $0, database: self.publicDB)
}
DispatchQueue.main.async {
completion(nil)
}
}
}
}
2. Establishment.swift
import UIKit
import MapKit
import CloudKit
import CoreLocation
class Establishment {
enum ChangingTable: Int {
case none
case womens
case mens
case both
}
static let recordType = "Establishment"
private let id: CKRecord.ID
let name: String
let location: CLLocation
let coverPhoto: CKAsset?
let database: CKDatabase
let changingTable: ChangingTable
let kidsMenu: Bool
let healthyOption: Bool
private(set) var notes: [Note]? = nil
init?(record: CKRecord, database: CKDatabase) {
guard
let name = record["name"] as? String,
let location = record["location"] as? CLLocation
else { return nil }
id = record.recordID
self.name = name
self.location = location
coverPhoto = record["coverPhoto"] as? CKAsset
self.database = database
healthyOption = record["healthyOption"] as? Bool ?? false
kidsMenu = record["kidsMenu"] as? Bool ?? false
if let changingTableValue = record["changingTable"] as? Int,
let changingTable = ChangingTable(rawValue: changingTableValue) {
self.changingTable = changingTable
} else {
self.changingTable = .none
}
if let noteRecords = record["notes"] as? [CKRecord.Reference] {
Note.fetchNotes(for: noteRecords) { notes in
self.notes = notes
}
}
}
func loadCoverPhoto(completion: @escaping (_ photo: UIImage?) -> ()) {
DispatchQueue.global(qos: .utility).async {
var image: UIImage?
defer {
DispatchQueue.main.async {
completion(image)
}
}
guard
let coverPhoto = self.coverPhoto,
let fileURL = coverPhoto.fileURL
else {
return
}
let imageData: Data
do {
imageData = try Data(contentsOf: fileURL)
} catch {
return
}
image = UIImage(data: imageData)
}
}
}
extension Establishment: Hashable {
static func == (lhs: Establishment, rhs: Establishment) -> Bool {
return lhs.id == rhs.id
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
3. Note.swift
import Foundation
import CloudKit
class Note {
private let id: CKRecord.ID
private(set) var noteLabel: String?
let establishmentReference: CKRecord.Reference?
init(record: CKRecord) {
id = record.recordID
noteLabel = record["text"] as? String
establishmentReference = record["establishment"] as? CKRecord.Reference
}
static func fetchNotes(_ completion: @escaping (Result<[Note], Error>) -> Void) {
let query = CKQuery(recordType: "Note",
predicate: NSPredicate(value: true))
let container = CKContainer.default()
container.privateCloudDatabase.perform(query, inZoneWith: nil) { results, error in
if let error = error {
DispatchQueue.main.async {
completion(.failure(error))
}
return
}
guard let results = results else {
DispatchQueue.main.async {
let error = NSError(
domain: "com.babifud", code: -1,
userInfo: [NSLocalizedDescriptionKey:
"Could not download notes"])
completion(.failure(error))
}
return
}
let notes = results.map(Note.init)
DispatchQueue.main.async {
completion(.success(notes))
}
}
}
static func fetchNotes(for references: [CKRecord.Reference], _ completion: @escaping ([Note]) -> Void) {
let recordIDs = references.map { $0.recordID }
let operation = CKFetchRecordsOperation(recordIDs: recordIDs)
operation.qualityOfService = .utility
operation.fetchRecordsCompletionBlock = { records, error in
let notes = records?.values.map(Note.init) ?? []
DispatchQueue.main.async {
completion(notes)
}
}
Model.currentModel.privateDB.add(operation)
}
}
extension Note: Hashable {
static func == (lhs: Note, rhs: Note) -> Bool {
return lhs.id == rhs.id
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
4. NearbyCell.swift
import UIKit
class NearbyCell: UITableViewCell {
// MARK: - Outlets
@IBOutlet private weak var placeImageView: UIImageView!
@IBOutlet private weak var name: UILabel!
var establishment: Establishment? {
didSet {
placeImageView.image = nil
name.text = establishment?.name
establishment?.loadCoverPhoto { [weak self] image in
guard let self = self else { return }
self.placeImageView.image = image
}
}
}
}
5. NearbyTableViewController.swift
import UIKit
import CoreLocation
class NearbyTableViewController: UITableViewController {
var locationManager: CLLocationManager!
var dataSource: UITableViewDiffableDataSource?
override func viewDidLoad() {
super.viewDidLoad()
setupLocationManager()
dataSource = establishmentDataSource()
tableView.dataSource = dataSource
refreshControl = UIRefreshControl()
refreshControl?.addTarget(self, action: #selector(refresh), for: .valueChanged)
refresh()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
reloadSnapshot(animated: false)
}
@objc private func refresh() {
Model.currentModel.refresh { error in
if let error = error {
let alert = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
self.tableView.refreshControl?.endRefreshing()
return
}
self.tableView.refreshControl?.endRefreshing()
self.reloadSnapshot(animated: true)
}
}
// MARK: - Navigation
@IBSegueAction private func detailSegue(coder: NSCoder, sender: Any?) -> DetailTableViewController? {
guard
let cell = sender as? NearbyCell,
let indexPath = tableView.indexPath(for: cell),
let detailViewController = DetailTableViewController(coder: coder)
else { return nil }
detailViewController.establishment = Model.currentModel.establishments[indexPath.row]
return detailViewController
}
}
extension NearbyTableViewController {
private func establishmentDataSource() -> UITableViewDiffableDataSource {
let reuseIdentifier = "NearbyCell"
return UITableViewDiffableDataSource(tableView: tableView) { (tableView, indexPath, establishment) -> NearbyCell? in
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) as? NearbyCell
cell?.establishment = establishment
return cell
}
}
private func reloadSnapshot(animated: Bool) {
var snapshot = NSDiffableDataSourceSnapshot()
snapshot.appendSections([0])
snapshot.appendItems(Model.currentModel.establishments)
dataSource?.apply(snapshot, animatingDifferences: animated)
if Model.currentModel.establishments.isEmpty {
let label = UILabel()
label.text = "No Restaurants Found"
label.textColor = UIColor.systemGray2
label.textAlignment = .center
label.font = UIFont.preferredFont(forTextStyle: .title2)
tableView.backgroundView = label
} else {
tableView.backgroundView = nil
}
}
}
// MARK: - CLLocationManagerDelegate
extension NearbyTableViewController: CLLocationManagerDelegate {
func setupLocationManager() {
locationManager = CLLocationManager()
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
// Only look at locations within a 0.5 km radius.
locationManager.distanceFilter = 500.0
locationManager.delegate = self
CLLocationManager.authorizationStatus()
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .notDetermined:
manager.requestWhenInUseAuthorization()
case .authorizedWhenInUse:
manager.startUpdatingLocation()
default:
// Do nothing.
print("Other status")
}
}
}
6. DetailTableViewController.swift
import UIKit
class DetailTableViewController: UITableViewController {
// MARK: - Outlets
@IBOutlet private weak var imageView: UIImageView!
@IBOutlet private weak var kidsMenuImageView: UIImageView!
@IBOutlet private weak var healthyOptionImageView: UIImageView!
@IBOutlet private weak var womensChangingLabel: UILabel!
@IBOutlet private weak var mensChangingLabel: UILabel!
// MARK: - Properties
var establishment: Establishment?
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
private func setup() {
guard let establishment = establishment else { return }
title = establishment.name
let circleFill = "checkmark.circle.fill"
let notAvailable = "xmark.circle"
var kidsImageName = circleFill
var healthyFoodName = circleFill
if !establishment.kidsMenu {
kidsImageName = notAvailable
}
if !establishment.healthyOption {
healthyFoodName = notAvailable
}
kidsMenuImageView.image = UIImage(systemName: kidsImageName)
healthyOptionImageView.image = UIImage(systemName: healthyFoodName)
let changingTable = establishment.changingTable
womensChangingLabel.alpha = (changingTable == .womens || changingTable == .both) ? 1.0 : 0.5
mensChangingLabel.alpha = (changingTable == .mens || changingTable == .both) ? 1.0 : 0.5
establishment.loadCoverPhoto { [weak self] image in
guard let self = self else { return }
self.imageView.image = image
}
}
// MARK: - Navigation
@IBSegueAction private func notesSegue(coder: NSCoder, sender: Any?) -> NotesTableViewController? {
guard let notesTableViewController = NotesTableViewController(coder: coder) else { return nil }
notesTableViewController.establishment = establishment
return notesTableViewController
}
}
7. NotesTableViewController.swift
import UIKit
import CloudKit
class NotesTableViewController: UITableViewController {
// MARK: - Properties
var notes: [Note] = []
var establishment: Establishment?
var dataSource: UITableViewDiffableDataSource?
override func viewDidLoad() {
super.viewDidLoad()
dataSource = notesDataSource()
if establishment == nil {
refreshControl = UIRefreshControl()
refreshControl?.addTarget(self, action: #selector(refresh), for: .valueChanged)
refresh()
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
reloadSnapshot(animated: false)
}
@objc private func refresh() {
Note.fetchNotes { result in
self.refreshControl?.endRefreshing()
switch result {
case .failure(let error):
let alert = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
self.present(alert, animated: true, completion: nil)
case .success(let notes):
self.notes = notes
}
self.reloadSnapshot(animated: true)
}
}
}
extension NotesTableViewController {
private func notesDataSource() -> UITableViewDiffableDataSource {
let reuseIdentifier = "NoteCell"
return UITableViewDiffableDataSource(tableView: tableView) { (tableView, indexPath, note) -> UITableViewCell? in
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath)
cell.textLabel?.text = note.noteLabel
return cell
}
}
private func reloadSnapshot(animated: Bool) {
var snapshot = NSDiffableDataSourceSnapshot()
snapshot.appendSections([0])
var isEmpty = false
if let establishment = establishment {
snapshot.appendItems(establishment.notes ?? [])
isEmpty = (establishment.notes ?? []).isEmpty
} else {
snapshot.appendItems(notes)
isEmpty = notes.isEmpty
}
dataSource?.apply(snapshot, animatingDifferences: animated)
if isEmpty {
let label = UILabel()
label.text = "No Notes Found"
label.textColor = UIColor.systemGray2
label.textAlignment = .center
label.font = UIFont.preferredFont(forTextStyle: .title2)
tableView.backgroundView = label
} else {
tableView.backgroundView = nil
}
}
}
后记
本篇主要讲述了CloudKit一个基本使用示例,感兴趣的给个赞或者关注~~~