### Install Nimble via CocoaPods Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Add Nimble to your Podfile to integrate it into your project. Ensure 'use_frameworks!' is included for Swift support. Run 'pod install' after updating the Podfile. ```ruby platform :ios, '8.0' source 'https://github.com/CocoaPods/Specs.git' # Whatever pods you need for your app go here target 'YOUR_APP_NAME_HERE_Tests', :exclusive => true do use_frameworks! pod 'Nimble', '~> 6.0.0' end ``` -------------------------------- ### Asynchronous Expectations in Swift (2.3-) Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md This example demonstrates asynchronous expectations using the older Swift 2.3 dispatch queue syntax. ```Swift // Swift 2.3 and earlier dispatch_async(dispatch_get_main_queue()) { ocean.add("dolphins") ocean.add("whales") } expect(ocean).toEventually(contain("dolphins", "whales")) ``` -------------------------------- ### Install SwiftyCloudKit with CocoaPods Source: https://context7.com/simengangstad/swiftycloudkit/llms.txt Add this line to your Podfile to include SwiftyCloudKit in your project. ```ruby # Podfile pod 'SwiftyCloudKit' ``` -------------------------------- ### Example Upload and Delete Operations Source: https://github.com/simengangstad/swiftycloudkit/blob/master/README.md Demonstrates creating a CKRecord, setting a string value, and then uploading and deleting it using CloudKitHandler. Includes priority and completion handlers. ```swift let record = CKRecord(recordType: MyRecordType) record.set(string: "Hello World", key: MyStringKey) upload(records: [record], withPriority: .userInitiated, perRecordProgress: nil) { (uploadedRecords, error) in // Do something with the uploaded record }) delete(records: [record], withPriority: .userInitiated, perRecordProgress: nil) { (deletedRecordIDs, error) in // Do something when the record is deleted }) ``` -------------------------------- ### Define a Custom BeNil Matcher in Swift Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Example of creating a custom 'beNil' matcher. It utilizes Predicate.simpleNilable for concise nil-checking and automatic message generation. ```swift public func beNil() -> Predicate { // Predicate.simpleNilable(..) automatically generates ExpectationMessage for // us based on the string we provide to it. Also, the 'Nilable' postfix indicates // that this Predicate supports matching against nil actualExpressions, instead of // always resulting in a PredicateStatus.fail result -- which is true for // Predicate.simple(..) return Predicate.simpleNilable("be nil") { actualExpression in let actualValue = try actualExpression.evaluate() return PredicateStatus(bool: actualValue == nil) } } ``` -------------------------------- ### CloudKitFetcher - Fetch Records Source: https://github.com/simengangstad/swiftycloudkit/blob/master/README.md Fetches records from iCloud. Requires setup in CloudKit Dashboard and implementation of specific variables to define the fetch execution. ```APIDOC ## CloudKitFetcher - Fetch Records ### Description Fetches records from iCloud. Requires setup in CloudKit Dashboard and implementation of specific variables to define the fetch execution. ### Variables Required - `database`: CKDatabase - `query`: CKQuery? - `interval`: Int - `cursor`: CKQueryOperation.Cursor? - `zoneID`: CKRecordZone.ID - `desiredKeys`: [String]? ### Method `fetch(withCompletionHandler: (([CKRecord]?, Error?) -> Void)?)` ### Request Example ```swift fetch(withCompletionHandler: { (records, error) in // Do something with the fetched records. }) ``` ### Response #### Success Response - `records`: [CKRecord]? - Fetched CloudKit records. - `error`: Error? - An error object if the fetch failed. ``` -------------------------------- ### Check Collection Order with Nimble Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Use `beginWith` to assert that a collection starts with specific elements and `endWith` to assert it ends with them. Similar to `contain`, Objective-C versions currently support only a single argument. ```swift // Swift // Passes if the elements in expected appear at the beginning of 'actual': expect(actual).to(beginWith(expected...)) // Passes if the the elements in expected come at the end of 'actual': expect(actual).to(endWith(expected...)) ``` ```objc // Objective-C // Passes if the elements in expected appear at the beginning of 'actual': expect(actual).to(beginWith(expected)); // Passes if the the elements in expected come at the end of 'actual': expect(actual).to(endWith(expected)); ``` -------------------------------- ### Define a Custom Equal Matcher in Swift Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Example of creating a custom 'equal' matcher function. It returns a Predicate closure that compares an expected value with the actual value. ```swift public func equal(expectedValue: T?) -> Predicate { // Can be shortened to: // Predicate { actual in ... } // // But shown with types here for clarity. return Predicate { (actual: Expression) throws -> PredicateResult in let msg = ExpectationMessage.expectedActualValueTo("equal <\(expectedValue)>") if let actualValue = try actualExpression.evaluate() { return PredicateResult( bool: actualValue == expectedValue!, message: msg ) } else { return PredicateResult( status: .fail, message: msg.appendedBeNilHint() ) } } } ``` -------------------------------- ### Subscribe and Unsubscribe to Updates Source: https://github.com/simengangstad/swiftycloudkit/blob/master/README.md Subscribe to and unsubscribe from CloudKit updates. Subscriptions can be expensive, so it's recommended to manage them carefully, for example, by subscribing in viewDidAppear and unsubscribing in viewDidDisappear. ```swift // Call in e.g. viewDidAppear subscribe(_ completionHandler: ((CKError?) -> Void)?) // Call in e.g. viewDidDisappear unsubscribe(_ completionHandler: ((CKError?) -> Void)?) ``` -------------------------------- ### Asynchronous Expectations in Swift (3.0+) Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Use `toEventually` to make expectations on values updated asynchronously. This example uses Swift 3.0+ syntax for dispatch queues. ```Swift // Swift 3.0 and later DispatchQueue.main.async { ocean.add("dolphins") ocean.add("whales") } expect(ocean).toEventually(contain("dolphins", "whales")) ``` -------------------------------- ### Set and Get Scalar Values on CKRecord Source: https://context7.com/simengangstad/swiftycloudkit/llms.txt Use these helpers to set and get typed values on CKRecord without explicit casting. Ensure the correct type is used for both setting and retrieving. ```swift let record = CKRecord(recordType: "Profile") // String record.set(string: "Alice", key: "name") let name: String? = record.string("name") // "Alice" // Int record.set(int: 42, key: "age") let age: Int? = record.int("age") // 42 // Double record.set(double: 1.75, key: "height") let height: Double? = record.double("height") // Date record.set(date: Date(), key: "birthday") let birthday: Date? = record.date("birthday") // CLLocation import CoreLocation record.set(location: CLLocation(latitude: 59.91, longitude: 10.75), key: "coords") let coords: CLLocation? = record.location("coords") // Data / CKAsset record.set(data: Data([0x01, 0x02]), key: "blob") let blob: Data? = record.data("blob") as? Data // List types record.set(strings: ["swift", "ios"], key: "tags") let tags: [String]? = record.strings("tags") record.set(ints: [1, 2, 3], key: "scores") let scores: [Int]? = record.ints("scores") ``` -------------------------------- ### Swift Exception Testing (Conceptual) Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md While Nimble has syntax for `raiseException()`, Swift does not currently support exceptions in a way that Nimble can catch. These examples are illustrative of the intended usage if Swift exceptions were supported. ```swift // Swift // Passes if 'actual', when evaluated, raises an exception: expect(actual).to(raiseException()) // Passes if 'actual' raises an exception with the given name: expect(actual).to(raiseException(named: name)) // Passes if 'actual' raises an exception with the given name and reason: expect(actual).to(raiseException(named: name, reason: reason)) // Passes if 'actual' raises an exception which passes expectations defined in the given closure: // (in this case, if the exception's name begins with "a r") expect { exception.raise() }.to(raiseException { (exception: NSException) in expect(exception.name).to(beginWith("a r")) }) ``` -------------------------------- ### Custom NonNilMatcherFunc in Swift Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Example of creating a custom matcher using `NonNilMatcherFunc` in Swift. This matcher checks if a sequence begins with a specific element and does not match nil values. ```swift public func beginWith(startingElement: T) -> NonNilMatcherFunc { return NonNilMatcherFunc { actualExpression, failureMessage in failureMessage.postfixMessage = "begin with <\(startingElement)>" if let actualValue = actualExpression.evaluate() { var actualGenerator = actualValue.makeIterator() return actualGenerator.next() == startingElement } return false } } ``` -------------------------------- ### Video Helpers Source: https://context7.com/simengangstad/swiftycloudkit/llms.txt Enables storing and retrieving video files as `CKAsset`. ```APIDOC ## Video helpers — Store and retrieve video as `CKAsset` ```swift let record = CKRecord(recordType: "Post") // Store a local video URL if let videoURL = Bundle.main.url(forResource: "intro", withExtension: "mov") { record.set(video: videoURL, key: "introVideo") } // Retrieve as a local temporary URL suitable for AVPlayer import AVKit if let localVideoURL = record.video("introVideo") { let player = AVPlayer(url: localVideoURL) let controller = AVPlayerViewController() controller.player = player present(controller, animated: true) { player.play() } } // Clean up cached videos when the app terminates func applicationWillTerminate(_ application: UIApplication) { deleteLocalVideos() } ``` ``` -------------------------------- ### Migrating to Predicate via .predicate Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Swift code demonstrating the minimal migration step to support the `Predicate` API by converting an old matcher type using the `.predicate` extension. ```swift public func beginWith(startingElement: T) -> Predicate { return NonNilMatcherFunc { actualExpression, failureMessage in failureMessage.postfixMessage = "begin with <\(startingElement)>" if let actualValue = actualExpression.evaluate() { var actualGenerator = actualValue.makeIterator() return actualGenerator.next() == startingElement } return false }.predicate } ``` -------------------------------- ### Objective-C Nil Handling with Nimble Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Demonstrates the correct way to assert nil expectations in Objective-C using Nimble. `beNil()` passes for nil, while `equal(nil)` fails. ```objc expect(nil).to(equal(nil)); // fails expect(nil).to(beNil()); // passes ``` -------------------------------- ### Post-build Script to Remove libswiftXCTest.dylib Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md A post-build script action for Xcode schemes to remove the libswiftXCTest.dylib file. This is necessary to avoid issues when using Nimble in a standalone app without XCTest. ```shell rm "${SWIFT_STDLIB_TOOL_DESTINATION_DIR}/libswiftXCTest.dylib" ``` -------------------------------- ### Perform Numerical Comparisons in Objective-C Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Use matchers like `beLessThan`, `beLessThanOrEqualTo`, `beGreaterThan`, and `beGreaterThanOrEqualTo` for numerical comparisons in Objective-C. Values must conform to `Comparable`. ```objc expect(actual).to(beLessThan(expected)); expect(actual).to(beLessThanOrEqualTo(expected)); expect(actual).to(beGreaterThan(expected)); expect(actual).to(beGreaterThanOrEqualTo(expected)); ``` -------------------------------- ### Register for Remote Notifications in AppDelegate Source: https://context7.com/simengangstad/swiftycloudkit/llms.txt Ensures your application is registered to receive remote notifications from iCloud. This setup is crucial for receiving push notifications for database changes. ```swift // AppDelegate.swift import UIKit import CloudKit import SwiftyCloudKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { application.registerForRemoteNotifications() return true } func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { let ckqn = CKQueryNotification(fromRemoteNotificationDictionary: userInfo as! [String: NSObject]) let notification = Notification( name: NSNotification.Name(rawValue: CloudKitNotifications.NotificationReceived), object: self, userInfo: [CloudKitNotifications.NotificationKey: ckqn] ) NotificationCenter.default.post(notification) } } ``` -------------------------------- ### Image Helpers Source: https://context7.com/simengangstad/swiftycloudkit/llms.txt Facilitates storing and retrieving `UIImage` objects as `CKAsset`. ```APIDOC ## Image helpers — Store and retrieve `UIImage` as `CKAsset` ```swift let record = CKRecord(recordType: "Post") // Store a UIImage (compressed to JPEG at 70% quality by default) record.set(image: UIImage(named: "cover"), key: "coverPhoto") // Retrieve it back as UIImage if let coverPhoto: UIImage = record.image("coverPhoto") { imageView.image = coverPhoto } // Use PNG instead do { record["avatar"] = try CKAsset(image: profileImage, fileType: .PNG) } catch ImageError.UnableToConvertImageToData { print("Image conversion failed") } ``` ``` -------------------------------- ### Store and Retrieve Video as CKAsset Source: https://context7.com/simengangstad/swiftycloudkit/llms.txt Store local video URLs as `CKAsset`s and retrieve them as temporary URLs suitable for `AVPlayer`. Remember to clean up cached videos when the app terminates. ```swift let record = CKRecord(recordType: "Post") // Store a local video URL if let videoURL = Bundle.main.url(forResource: "intro", withExtension: "mov") { record.set(video: videoURL, key: "introVideo") } // Retrieve as a local temporary URL suitable for AVPlayer import AVKit if let localVideoURL = record.video("introVideo") { let player = AVPlayer(url: localVideoURL) let controller = AVPlayerViewController() controller.player = player present(controller, animated: true) { player.play() } } // Clean up cached videos when the app terminates func applicationWillTerminate(_ application: UIApplication) { deleteLocalVideos() } ``` -------------------------------- ### Store and Retrieve UIImage as CKAsset Source: https://context7.com/simengangstad/swiftycloudkit/llms.txt Store `UIImage` objects as `CKAsset`s, with default JPEG compression. Retrieve them back as `UIImage` or use `CKAsset` initializer for custom compression types like PNG. ```swift let record = CKRecord(recordType: "Post") // Store a UIImage (compressed to JPEG at 70% quality by default) record.set(image: UIImage(named: "cover"), key: "coverPhoto") // Retrieve it back as UIImage if let coverPhoto: UIImage = record.image("coverPhoto") { imageView.image = coverPhoto } // Use PNG instead do { record["avatar"] = try CKAsset(image: profileImage, fileType: .PNG) } catch ImageError.UnableToConvertImageToData { print("Image conversion failed") } ``` -------------------------------- ### Generic Type Constrained Matcher in Swift Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Define a matcher that constrains the type of the actual value using Swift generics. This example requires the actual value to conform to the Printable protocol. ```swift public func haveDescription(description: String) -> Predicate { return Predicate.simple("have description") { actual in return PredicateStatus(bool: actual.evaluate().description == description) } } ``` -------------------------------- ### Objective-C Usage of Custom Matcher Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Demonstrates how to use a custom Nimble matcher, previously defined for Objective-C compatibility, within Objective-C code. This simplifies the syntax for using Swift-based matchers. ```objective-c expect(actual).to([NMBObjCMatcher beNilMatcher]()); ``` ```objective-c FOUNDATION_EXPORT id beNil() { return [NMBObjCMatcher beNilMatcher]; } ``` -------------------------------- ### Objective-C Type Conversions with Nimble Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Demonstrates how Nimble automatically converts C numeric types, NSRange, and char* to their Objective-C equivalents for assertions. Ensure parameters are Objective-C objects or convertible. ```objc @import Nimble; expect(@(1 + 1)).to(equal(@2)); expect(@"Hello world").to(contain(@"world")); // Boxed as NSNumber * expect(2).to(equal(2)); expect(1.2).to(beLessThan(2.0)); expect(true).to(beTruthy()); // Boxed as NSString * expect(@"Hello world").to(equal(@"Hello world")); // Boxed as NSRange expect(NSMakeRange(1, 10)).to(equal(NSMakeRange(1, 10))); ``` -------------------------------- ### Basic Equality Expectation in Objective-C Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Use `expect(...).to(equal(...))` for basic equality assertions in Objective-C. Ensure Nimble is imported. ```objectivec @import Nimble; expect(seagull.squawk).to(equal(@"Squee!")); ``` -------------------------------- ### CloudKitFetcher Protocol: fetch(withCompletionHandler:) Source: https://context7.com/simengangstad/swiftycloudkit/llms.txt Fetch records in paginated batches. Conform to `CloudKitFetcher` by declaring the required properties, then call `fetch` to retrieve records. The fetcher uses `CKQueryOperation.Cursor` to page through results in batches of `interval` records. Any locally buffered records (saved or pending deletion offline) are automatically reconciled during the fetch. ```APIDOC ## CloudKitFetcher Protocol: fetch(withCompletionHandler:) ### Description Fetch records in paginated batches. Conform to `CloudKitFetcher` by declaring the required properties, then call `fetch` to retrieve records. The fetcher uses `CKQueryOperation.Cursor` to page through results in batches of `interval` records. Any locally buffered records (saved or pending deletion offline) are automatically reconciled during the fetch. ### Method Signature `fetch(withCompletionHandler: (([CKRecord]?, Error?) -> Void)?) ### Usage Example ```swift import UIKit import CloudKit import SwiftyCloudKit class RecordsViewController: UITableViewController, CloudKitFetcher { // Required CloudKitFetcher properties var database: CKDatabase = CKContainer.default().privateCloudDatabase var query: CKQuery? { let q = CKQuery(recordType: "Note", predicate: NSPredicate(format: "TRUEPREDICATE")) q.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)] return q } var interval: Int = 20 var cursor: CKQueryOperation.Cursor? = nil var zoneID: CKRecordZone.ID = CKRecordZone.default().zoneID var desiredKeys: [String]? = nil // nil = fetch all keys var records = [CKRecord]() override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) cursor = nil // Reset to fetch from the beginning fetch { [weak self] (fetchedRecords, error) in DispatchQueue.main.async { if let error = error { print("Fetch error: \(error.localizedDescription)") return } self?.records = fetchedRecords ?? [] self?.tableView.reloadData() // cursor is non-nil if more pages are available; call fetch again to load the next page } } } } ``` ``` -------------------------------- ### Basic Equality Expectation in Swift Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Use `expect(...).to(equal(...))` for basic equality assertions in Swift. Ensure Nimble is imported. ```swift import Nimble expect(seagull.squawk).to(equal("Squee!")) ``` -------------------------------- ### Conform to Subscription Protocol Source: https://github.com/simengangstad/swiftycloudkit/blob/master/README.md Conform to the subscription protocol to handle incoming CloudKit notifications. This is a necessary step after registering for remote notifications. ```swift var subscription: CKQuerySubscription func handleSubscriptionNotification(ckqn: CKQueryNotification) {} ``` -------------------------------- ### Fetch Records with CloudKitFetcher Source: https://context7.com/simengangstad/swiftycloudkit/llms.txt Conform to CloudKitFetcher and implement required properties to fetch records in paginated batches. Locally buffered records are reconciled during fetch. ```swift import UIKit import CloudKit import SwiftyCloudKit class RecordsViewController: UITableViewController, CloudKitFetcher { // Required CloudKitFetcher properties var database: CKDatabase = CKContainer.default().privateCloudDatabase var query: CKQuery? { let q = CKQuery(recordType: "Note", predicate: NSPredicate(format: "TRUEPREDICATE")) q.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)] return q } var interval: Int = 20 var cursor: CKQueryOperation.Cursor? = nil var zoneID: CKRecordZone.ID = CKRecordZone.default().zoneID var desiredKeys: [String]? = nil // nil = fetch all keys var records = [CKRecord]() override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) cursor = nil // Reset to fetch from the beginning fetch { [weak self] (fetchedRecords, error) in DispatchQueue.main.async { if let error = error { print("Fetch error: \(error.localizedDescription)") return } self?.records = fetchedRecords ?? [] self?.tableView.reloadData() // cursor is non-nil if more pages are available; call fetch again to load the next page } } } } ``` -------------------------------- ### Global Configuration: offlineSupport Source: https://context7.com/simengangstad/swiftycloudkit/llms.txt Controls whether the library stores pending uploads/deletions locally when there is no network connection, replaying them on the next `fetch` call. ```APIDOC ## Global Configuration: offlineSupport ### Description Controls whether the library stores pending uploads/deletions locally when there is no network connection, replaying them on the next `fetch` call. ### Usage ```swift import SwiftyCloudKit // Enable offline support (default: true) offlineSupport = true // Disable if you want operations to fail immediately without a connection offlineSupport = false ``` ``` -------------------------------- ### Perform Numerical Comparisons in Swift Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Use comparison operators or matchers like `beLessThan`, `beLessThanOrEqualTo`, `beGreaterThan`, and `beGreaterThanOrEqualTo` for numerical comparisons. Values must conform to `Comparable`. ```swift expect(actual).to(beLessThan(expected)) expect(actual) < expected expect(actual).to(beLessThanOrEqualTo(expected)) expect(actual) <= expected expect(actual).to(beGreaterThan(expected)) expect(actual) > expected expect(actual).to(beGreaterThanOrEqualTo(expected)) expect(actual) >= expected ``` -------------------------------- ### Raising Exception Expectation in Objective-C (Specific) Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Customize exception matching in Objective-C using `raiseException` with named, reason, and userInfo parameters. Ensure Nimble is imported. ```objectivec // Use the property-block syntax to be more specific. expectAction(^{ [exception raise]; }).to(raiseException().named(NSInternalInconsistencyException)); expectAction(^{ [exception raise]; }).to(raiseException(). named(NSInternalInconsistencyException). reason("Not enough fish in the sea")); expectAction(^{ [exception raise]; }).to(raiseException(). named(NSInternalInconsistencyException). reason("Not enough fish in the sea"). userInfo(@{@"something": @"is fishy"})); ``` -------------------------------- ### Subscribe and Unsubscribe to Database Changes in ViewController Source: https://context7.com/simengangstad/swiftycloudkit/llms.txt Manages real-time subscriptions to 'Note' records. Subscribes in `viewDidAppear` and unsubscribes in `viewDidDisappear` to optimize resource usage. Requires remote-notification background mode and AppDelegate registration. ```swift // ViewController.swift class NotesViewController: UITableViewController, CloudKitSubscriber { var database: CKDatabase = CKContainer.default().privateCloudDatabase var records = [CKRecord]() var subscription: CKQuerySubscription { let sub = CKQuerySubscription( recordType: "Note", predicate: NSPredicate(format: "TRUEPREDICATE"), subscriptionID: "all-notes", options: [.firesOnRecordCreation, .firesOnRecordDeletion, .firesOnRecordUpdate] ) let info = CKSubscription.NotificationInfo() info.alertLocalizationKey = "Notes updated" info.shouldBadge = true sub.notificationInfo = info return sub } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) subscribe { error in print(error?.localizedDescription ?? "Subscribed") } } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) unsubscribe { error in print(error?.localizedDescription ?? "Unsubscribed") } } func handleSubscriptionNotification(ckqn: CKQueryNotification) { guard let recordID = ckqn.recordID else { return } switch ckqn.queryNotificationReason { case .recordCreated: database.fetch(withRecordID: recordID) { record, _ in if let record = record { DispatchQueue.main.async { self.records.insert(record, at: 0) self.tableView.insertRows(at: [IndexPath(row: 0, section: 0)], with: .top) } } } case .recordDeleted: DispatchQueue.main.async { if let index = self.records.firstIndex(where: { $0.recordID == recordID }) { self.records.remove(at: index) self.tableView.deleteRows(at: [IndexPath(row: index, section: 0)], with: .fade) } } case .recordUpdated: database.fetch(withRecordID: recordID) { record, _ in if let record = record, let index = self.records.firstIndex(where: { $0.recordID == record.recordID }) { DispatchQueue.main.async { self.records[index] = record self.tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: .automatic) } } } @unknown default: break } } } ``` -------------------------------- ### Define CloudKitFetcher Properties Source: https://github.com/simengangstad/swiftycloudkit/blob/master/README.md These properties define how the CloudKitFetcher executes queries. Ensure a record type is set up in the CloudKit Dashboard. ```swift var database: CKDatabase var query: CKQuery? var interval: Int var cursor: CKQueryOperation.Cursor? var zoneID: CKRecordZone.ID var desiredKeys: [String]? ``` -------------------------------- ### Objective-C: All Elements Pass Matcher Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Use `allPass` in Objective-C to assert that all elements in a collection satisfy a given matcher. The collection must implement `NSFastEnumeration` and its elements must subclass `NSObject`. ```objc expect(@[@1, @2, @3, @4]).to(allPass(beLessThan(@5))); ``` -------------------------------- ### Nimble String Matchers Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Nimble provides matchers for checking string containment, prefixes, suffixes, emptiness, and regular expression matches. These are available for both Swift and Objective-C. ```swift // Swift // Passes if 'actual' contains 'substring': expect(actual).to(contain(substring)) // Passes if 'actual' begins with 'prefix': expect(actual).to(beginWith(prefix)) // Passes if 'actual' ends with 'suffix': expect(actual).to(endWith(suffix)) // Passes if 'actual' represents the empty string, ": expect(actual).to(beEmpty()) // Passes if 'actual' matches the regular expression defined in 'expected': expect(actual).to(match(expected)) ``` ```objc // Objective-C // Passes if 'actual' contains 'substring': expect(actual).to(contain(expected)); // Passes if 'actual' begins with 'prefix': expect(actual).to(beginWith(prefix)); // Passes if 'actual' ends with 'suffix': expect(actual).to(endWith(suffix)); // Passes if 'actual' represents the empty string, ": expect(actual).to(beEmpty()); // Passes if 'actual' matches the regular expression defined in 'expected': expect(actual).to(match(expected)) ``` -------------------------------- ### Basic Nimble Assertions in Swift Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Use Nimble's `expect(...).to(...)` syntax for various assertion types, including equality, approximate equality, comparisons, and containment. For asynchronous operations, use `toEventually(...)`. ```swift expect(1 + 1).to(equal(2)) expect(1.2).to(beCloseTo(1.1, within: 0.1)) expect(3) > 2 expect("seahorse").to(contain("sea")) expect(["Atlantic", "Pacific"]).toNot(contain("Mississippi")) expect(ocean.isClean).toEventually(beTruthy()) ``` -------------------------------- ### retryUploadAfter(error:withRecords:priority:perRecordProgress:andCompletionHandler:) Source: https://context7.com/simengangstad/swiftycloudkit/llms.txt Schedules a retry for an upload operation after a CloudKit rate-limit error, using the interval provided by CKErrorRetryAfterKey. ```APIDOC ## retryUploadAfter(error:withRecords:priority:perRecordProgress:andCompletionHandler:) ### Description Schedules a retry using the `CKErrorRetryAfterKey` interval provided in the `CKError`. Useful for handling `.serviceUnavailable` or `.requestRateLimited` errors explicitly. ### Parameters - `error` (CKError) - The CloudKit error that occurred, expected to contain a retry interval. - `withRecords` ([`CKRecord`]) - The array of `CKRecord` objects that failed to upload. - `priority` (OperationPriority) - The priority for the retry operation. - `perRecordProgress` (([`CKRecord`], Double) -> Void)? - A closure for per-record progress during the retry. - `andCompletionHandler` (([CKRecord]?, Error?) -> Void)? - A closure called upon completion of the retry attempt. ``` -------------------------------- ### Nimble Collection Element Checks (Swift) Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md In Swift, use `allPass` to ensure every element in a `Sequence` collection satisfies a given condition, either through a closure or by composing with another matcher like `beLessThan`. ```swift // Swift // Providing a custom function: expect([1, 2, 3, 4]).to(allPass { $0! < 5 }) // Composing the expectation with another matcher: expect([1, 2, 3, 4]).to(allPass(beLessThan(5))) ``` -------------------------------- ### Objective-C Matcher with Nil Check Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md An Objective-C matcher factory function that creates a matcher using `NMBObjCMatcher`. It explicitly sets `canMatchNil` to false and uses a Swift `beginWith` matcher. ```swift extension NMBObjCMatcher { public class func beginWithMatcher(expected: AnyObject) -> NMBObjCMatcher { return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in let actual = actualExpression.evaluate() let expr = actualExpression.cast { $0 as? NMBOrderedCollection } return beginWith(expected).matches(expr, failureMessage: failureMessage) } } } ``` -------------------------------- ### Fetch Records with CloudKitFetcher Source: https://github.com/simengangstad/swiftycloudkit/blob/master/README.md Call this function to fetch records from iCloud. The completion handler provides fetched records or an error. ```swift fetch(withCompletionHandler: { (records, error) in // Do something with the fetched records. }) ``` -------------------------------- ### Generate Web Tokens for Restrictions Source: https://github.com/simengangstad/swiftycloudkit/blob/master/README.md Generate web tokens for containers with API tokens. These web tokens are required for restricting databases. ```swift restrictTokens(forContainersWithAPITokens containerTokens: [CKContainer: String]) -> [CKContainer:String] ``` -------------------------------- ### upload(records:withPriority:perRecordProgress:andCompletionHandler:) Source: https://context7.com/simengangstad/swiftycloudkit/llms.txt Uploads an array of CKRecord objects to the configured database. Supports offline buffering and per-record progress callbacks. ```APIDOC ## upload(records:withPriority:perRecordProgress:andCompletionHandler:) ### Description Uploads an array of `CKRecord` objects to the configured database. When `offlineSupport` is enabled and no connection is available, records are buffered locally and uploaded automatically on the next `fetch`. Per-record upload progress callbacks are supported. ### Parameters - `records` ([`CKRecord`]) - An array of `CKRecord` objects to upload. - `withPriority` (OperationPriority) - The priority of the upload operation. - `perRecordProgress` (([`CKRecord`], Double) -> Void)? - A closure that is called for each record's upload progress. - `andCompletionHandler` (([CKRecord]?, Error?) -> Void)? - A closure that is called when the upload operation is complete. ``` -------------------------------- ### Retrieve All User Records Across Containers Source: https://context7.com/simengangstad/swiftycloudkit/llms.txt Use `retrieveRecords` to fetch all records of specified types owned by the current user across public and private databases. This is useful for data-access requests. ```swift import SwiftyCloudKit import CloudKit let defaultContainer = CKContainer.default() let containerRecordTypes: [CKContainer: [String]] = [ defaultContainer: ["Note", "Profile"] ] let allRecords: [CKContainer: [CKRecord]] = retrieveRecords(containerRecordTypes: containerRecordTypes) for (container, records) in allRecords { print("\(container.containerIdentifier!): \(records.count) record(s)") } ``` -------------------------------- ### Retrieve All User Data Source: https://github.com/simengangstad/swiftycloudkit/blob/master/README.md Retrieve a copy of all user data across specified containers. This function is part of the user data management helpers. ```swift retrieveRecords(containerRecordTypes: [CKContainer: [String]]) -> [CKContainer: [CKRecord]] ``` -------------------------------- ### Supporting Nimble Matchers in Objective-C Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Extend NMBObjCMatcher in Swift to create Objective-C compatible matchers. This involves defining a class method that returns an NMBObjCMatcher instance, which wraps the Swift matcher logic. ```swift extension NMBObjCMatcher { public class func beNilMatcher() -> NMBObjCMatcher { return NMBObjCMatcher { actualBlock, failureMessage, location in let block = ({ actualBlock() as NSObject? }) let expr = Expression(expression: block, location: location) return beNil().matches(expr, failureMessage: failureMessage) } } } ``` -------------------------------- ### retrieveRecords(containerRecordTypes:) Source: https://context7.com/simengangstad/swiftycloudkit/llms.txt Retrieves all user records across specified containers and record types. Useful for data-access requests. ```APIDOC ## `retrieveRecords(containerRecordTypes:)` — Retrieve all user records across containers Returns all records of the specified types owned by the current user, across both public and private databases, for compliance with data-access requests. ```swift import SwiftyCloudKit import CloudKit let defaultContainer = CKContainer.default() let containerRecordTypes: [CKContainer: [String]] = [ defaultContainer: ["Note", "Profile"] ] let allRecords: [CKContainer: [CKRecord]] = retrieveRecords(containerRecordTypes: containerRecordTypes) for (container, records) in allRecords { print("\(container.containerIdentifier!): \(records.count) record(s)") } ``` ``` -------------------------------- ### Raising Exception Expectation in Objective-C (Basic) Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Use `expectAction` to test if a block of Objective-C code raises an exception. Ensure Nimble is imported. ```objectivec NSException *exception = [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Not enough fish in the sea." userInfo:nil]; expectAction(^{ [exception raise]; }).to(raiseException()); ``` -------------------------------- ### Objective-C Exception Handling with Nimble Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Use expectAction for assertions on expressions that do not return a value, such as raising an exception. This ensures proper handling of side effects. ```objc // Objective-C expectAction(^{ [exception raise]; }).to(raiseException()); ``` -------------------------------- ### Test C Primitives in Swift Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Nimble directly supports testing primitive C values in Swift. Type inference can be used to simplify the syntax. ```Swift // Swift let actual: CInt = 1 let expectedValue: CInt = 1 expect(actual).to(equal(expectedValue)) ``` ```Swift // Swift expect(1 as CInt).to(equal(1)) ``` -------------------------------- ### Migrating to Predicate with Deprecated Closure Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Swift code showing how to convert to the `Predicate` type using a deprecated closure constructor. This method allows dropping old types but may slightly alter behavior. ```swift public func beginWith(startingElement: T) -> Predicate { return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in failureMessage.postfixMessage = "begin with <\(startingElement)>" if let actualValue = actualExpression.evaluate() { var actualGenerator = actualValue.makeIterator() return actualGenerator.next() == startingElement } return false } } ``` -------------------------------- ### Add SwiftyCloudKit as a Swift Package Manager dependency Source: https://context7.com/simengangstad/swiftycloudkit/llms.txt Include this dependency in your Package.swift file to use SwiftyCloudKit. ```swift // Package.swift dependencies: [ .package(url: "https://github.com/simengangstad/SwiftyCloudKit.git", from: "0.2.0") ] ``` -------------------------------- ### Swift: Posting Notifications Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Use `postNotifications` to verify that a closure posts a specific notification to the default or a given notification center. This matcher is only available in Swift. ```swift let testNotification = Notification(name: "Foo", object: nil) // passes if the closure in expect { ... } posts a notification to the default // notification center. expect { NotificationCenter.default.postNotification(testNotification) }.to(postNotifications(equal([testNotification])) ``` ```swift // passes if the closure in expect { ... } posts a notification to a given // notification center let notificationCenter = NotificationCenter() expect { notificationCenter.postNotification(testNotification) }.to(postNotifications(equal([testNotification]), fromNotificationCenter: notificationCenter)) ``` -------------------------------- ### Customizing Failure Messages with PredicateResult Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Achieve full customization of matcher behavior and failure messages by returning a PredicateResult. This allows for fine-grained control over the status and content of the failure message. ```swift // Emits standard error message: // eg - "expected to , got " case expectedActualValueTo(/* message: */ String) // Allows any free-form message // eg - "" case fail(/* message: */ String) // Emits standard error message with a custom actual value instead of the default. // eg - "expected to , got " case expectedCustomValueTo(/* message: */ String, /* actual: */ String) // Emits standard error message without mentioning the actual value // eg - "expected to " case expectedTo(/* message: */ String) // ... ``` -------------------------------- ### Custom Failure Messages in Swift Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Add custom failure messages to expectations in Swift using the `description` argument. This helps provide more context when a test fails. ```swift expect(1 + 1).to(equal(3)) // failed - expected to equal <3>, got <2> expect(1 + 1).to(equal(3), description: "Make sure libKindergartenMath is loaded") // failed - Make sure libKindergartenMath is loaded // expected to equal <3>, got <2> ``` -------------------------------- ### Upload Records to iCloud with SwiftyCloudKit Source: https://context7.com/simengangstad/swiftycloudkit/llms.txt Uploads an array of CKRecord objects to the configured database. Supports offline buffering and per-record progress callbacks. Ensure CloudKitHandler is properly initialized. ```swift import CloudKit import SwiftyCloudKit class NoteHandler: CloudKitHandler { var database: CKDatabase = CKContainer.default().privateCloudDatabase func saveNote(title: String, body: String, photo: UIImage?) { let record = CKRecord(recordType: "Note") record.set(string: title, key: "title") record.set(string: body, key: "body") if let photo = photo { record.set(image: photo, key: "photo") // auto-converts UIImage → CKAsset } record.set(date: Date(), key: "createdAt") upload( records: [record], withPriority: .userInitiated, perRecordProgress: { (record, progress) in print("Uploading \(record.recordID.recordName): \(Int(progress * 100))%") }, andCompletionHandler: { (uploadedRecords, error) in DispatchQueue.main.async { if let ckError = error as? CKError { print("CloudKit error: \(ckError.localizedDescription)") } else if let saved = uploadedRecords?.first { print("Saved record: \(saved.recordID.recordName)") } } } ) } } ``` -------------------------------- ### Custom Failure Messages in Objective-C Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Add custom failure messages to expectations in Objective-C using `toWithDescription`. This provides more context when a test fails. ```objectivec @import Nimble; expect(@(1+1)).to(equal(@3)); // failed - expected to equal <3.0000>, got <2.0000> expect(@(1+1)).toWithDescription(equal(@3), @"Make sure libKindergartenMath is loaded"); // failed - Make sure libKindergartenMath is loaded // expected to equal <3.0000>, got <2.0000> ``` -------------------------------- ### Check Object Identity in Objective-C Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Use `beIdenticalTo` to assert that two objects have the same pointer address in Objective-C. ```objc // Passes if 'actual' has the same pointer address as 'expected': expect(actual).to(beIdenticalTo(expected)); // Passes if 'actual' does not have the same pointer address as 'expected': expect(actual).toNot(beIdenticalTo(expected)); ``` -------------------------------- ### Configure Offline Support in SwiftyCloudKit Source: https://context7.com/simengangstad/swiftycloudkit/llms.txt Enable or disable the local buffering of pending uploads and deletions when offline. Defaults to true. ```swift import SwiftyCloudKit // Enable offline support (default: true) offlineSupport = true // Disable if you want operations to fail immediately without a connection offlineSupport = false ``` -------------------------------- ### Using `waitUntil` for Asynchronous Expectations in Swift Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md The `waitUntil` function in Swift provides a block-based approach to handle asynchronous operations and make expectations. The `done` callback must be invoked to signal completion. ```Swift // Swift waitUntil { done in ocean.goFish { success in expect(success).to(beTrue()) done() } } ``` -------------------------------- ### Set String Value in CKRecord Source: https://github.com/simengangstad/swiftycloudkit/blob/master/README.md Uses a helper function to set a string value for a specific key in a CKRecord. ```swift record.set(string: "Hello World", key: MyStringKey) ``` -------------------------------- ### Testing Exceptions in Objective-C with Nimble Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Use `raiseException()` to assert that a piece of code raises an exception. You can optionally specify the exception's name and reason, or provide a block to further assert properties of the caught exception. ```objc // Objective-C // Passes if 'actual', when evaluated, raises an exception: expect(actual).to(raiseException()) // Passes if 'actual' raises an exception with the given name expect(actual).to(raiseException().named(name)) // Passes if 'actual' raises an exception with the given name and reason: expect(actual).to(raiseException().named(name).reason(reason)) // Passes if 'actual' raises an exception and it passes expectations defined in the given block: // (in this case, if name begins with "a r") expect(actual).to(raiseException().satisfyingBlock(^(NSException *exception) { expect(exception.name).to(beginWith(@"a r")); })); ``` -------------------------------- ### Nimble `throwAssertion` Matcher in Swift Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Use `throwAssertion()` to verify that a block of Swift code throws an assertion, such as `fatalError()` or a failed `precondition`. This requires the CwlPreconditionTesting library and is only supported on x86_64 simulators. ```swift // Swift // Passes if 'somethingThatThrows()' throws an assertion, // such as by calling 'fatalError()' or if a precondition fails: expect { try somethingThatThrows() }.to(throwAssertion()) expect { () -> Void in fatalError() }.to(throwAssertion()) expect { precondition(false) }.to(throwAssertion()) // Passes if throwing an NSError is not equal to throwing an assertion: expect { throw NSError(domain: "test", code: 0, userInfo: nil) }.toNot(throwAssertion()) // Passes if the code after the precondition check is not run: var reachedPoint1 = false var reachedPoint2 = false expect { reachedPoint1 = true precondition(false, "condition message") reachedPoint2 = true }.to(throwAssertion()) expect(reachedPoint1) == true expect(reachedPoint2) == false ``` -------------------------------- ### Appending Details to Failure Messages Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Use helper functions like `appended(message:)` and `appended(details:)` to enrich failure messages. `appended(message:)` adds inline text, while `appended(details:)` adds multi-line details to test logs. ```swift // produces "expected to be true, got (use beFalse() for inverse)" // appended message do show up inline in Xcode. .expectedActualValueTo("be true").appended(message: " (use beFalse() for inverse)") ``` ```swift // produces "expected to be true, got \n\nuse beFalse() for inverse\nor use beNil()" // details do not show inline in Xcode, but do show up in test logs. .expectedActualValueTo("be true").appended(details: "use beFalse() for inverse\nor use beNil()") ``` -------------------------------- ### Asynchronous Expectations in Objective-C Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Objective-C asynchronous expectations are handled using `toEventually` with the appropriate dispatch queue syntax. ```Objective-C // Objective-C dispatch_async(dispatch_get_main_queue(), ^{ [ocean add:@"dolphins"]; [ocean add:@"whales"]; }); expect(ocean).toEventually(contain(@"dolphins", @"whales")); ``` -------------------------------- ### Check Floating Point Proximity in Objective-C Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Use `beCloseTo` with `.within(delta)` to assert that two floating-point numbers are close within a specified margin of error. Values must be coercible into `Double`. ```objc expect(actual).to(beCloseTo(expected).within(delta)); ``` ```objc expect(@(10.01)).to(beCloseTo(@10).within(0.1)); ``` -------------------------------- ### Objective-C Equivalence Assertions with equal Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Use the equal matcher in Objective-C to assert equivalence between two objects. Values must be Equatable, Comparable, or subclasses of NSObject. The equal matcher fails with nil values. ```objc // Objective-C // Passes if 'actual' is equivalent to 'expected': expect(actual).to(equal(expected)) // Passes if 'actual' is not equivalent to 'expected': expect(actual).toNot(equal(expected)) ``` -------------------------------- ### Negated Equality Expectation in Objective-C Source: https://github.com/simengangstad/swiftycloudkit/blob/master/Example/Pods/Nimble/README.md Use `toNot` or `notTo` to assert that a value is not equal to the expected value in Objective-C. Ensure Nimble is imported. ```objectivec @import Nimble; expect(seagull.squawk).toNot(equal(@"Oh, hello there!")); expect(seagull.squawk).notTo(equal(@"Oh, hello there!")); ```