I am trying to have all the users' locations stored in cloudkit, then downloaded by each device. I marked on the code in storeLocation where I get the error:
"Cannot convert value of type '(NSError?, [CKRecord]?)' (aka '(Optional, Optional>)') to expected argument type '(error: NSError?, records: [CKRecord]?) -> Void'"
//saves location in cloud kit //currently works well:
var locArray = [CKRecord]()
func storeLocation(location:CLLocation) {
let locationRecord = CKRecord(recordType: "location")
locationRecord.setObject(location, forKey: "location")
let publicData = CKContainer.defaultContainer().publicCloudDatabase
publicData.saveRecord(locationRecord) { (records, error) in
if error != nil {
print("error saving locations: \(error)")
} else {
print("Locations saved: \(records)")
loadLocation((error, self.locArray)) //where I get error******
}
}
}
//fetches location from cloud kit:
func loadLocation(completion: (error:NSError?, records:[CKRecord]?) -> Void)
{
let query = CKQuery(recordType: "Location", predicate: NSPredicate(value: true))
CKContainer.defaultContainer().publicCloudDatabase.performQuery(query, inZoneWithID: nil){
(records, error) in
if error != nil {
print("error fetching locations")
completion(error: error, records: nil)
} else {
print("found locations: \(records)")
completion(error: nil, records: records)
}
}
}
I believe instead of:
loadLocation((error, self.locArray))
You need to call this:
loadLocation() { (error, records) in
// do something here with the returned data.
}
Related
I am getting the error: "Value of type 'CKDatabase' has no member 'fetchUserRecordIDWithCompletionHandler'." Was it taken out of the newest version of Xcode or swift 3?
func fetchUserRecords()
{
let publicDB = CKContainer.default().publicCloudDatabase
publicDB.fetchUserRecordIDWithCompletionHandler { (userID, error) -> Void in
if let userID = userID {
let reference = CKReference(recordID: userID, action: .None)
let predicate = NSPredicate(format: "creatorUserRecordID == %#", reference)
let query = CKQuery(recordType: "Location", predicate: predicate)
CKContainer.default().publicCloudDatabase.perform(query, inZoneWith: nil){
(records, error) in
if error != nil {
print("error fetching user records: \(error)")
completion(error as NSError?, nil)
} else {
print("found user records")
completion(nil, records)
guard let records = records else {
return
}
for record in records
{
//delete records
}
}
}
}
}
}
Swift 3 changes the names of lots of function calls. The new function signature is
func fetchUserRecordID(completionHandler: #escaping (CKRecordID?, Error?) -> Void)
EDIT:
rmaddy reports that the function is a function of CKContainer, not CKDatabase.
I am trying to convert CKRecords downloaded from cloudkit back to the original form of data they were (in this case a CLLocation). I get the error "Cannot convert value of type 'CKRecord' to expected argument type 'CLLocation'" when I try to call the function on line 17.
func loadLocation(completion: (error:NSError?, records:[CKRecord]?) -> Void)
{
let query = CKQuery(recordType: "Location", predicate: NSPredicate(value: true))
CKContainer.defaultContainer().publicCloudDatabase.performQuery(query, inZoneWithID: nil){
(records, error) in
if error != nil {
print("error fetching locations: \(error)")
completion(error: error, records: nil)
} else {
print("found locations: \(records)")
completion(error: nil, records: records)
guard let records = records else {
return
}
for(var i = 0; i<records.count; i += 1)
{
addBoundry(records[i])
}
}
}
}
Save your record by setting a key of the record to your location:
recordThatYouAreSaving.setObject(yourLocation, forKey: "location")
Then to get it:
addBoundry(records[i]["location"] as! CLLocation)
Even if you ask for records whose value is a CLLocation, the result of the query is still an array of CKRecord, not an array of CLLocation. If a CKRecord contains a CLLocation, you need to extract it by calling objectForKey: and cast it to a CLLocation.
I am having trouble fetching the locations from cloudkit. The location gets uploaded, but when I try to have them printed out and loaded, they aren't downloaded. I don't get any errors.
This function uploads the location to CloudKit:
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
let location = locations.last
let center = CLLocationCoordinate2D(latitude: location!.coordinate.latitude, longitude: location!.coordinate.longitude)
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.015, longitudeDelta: 0.015))
self.mapView.setRegion(region, animated: true)
self.locationManager.stopUpdatingLocation()//
let locationRecord = CKRecord(recordType: "location")
locationRecord.setObject(location, forKey: "location")
let publicData = CKContainer.defaultContainer().publicCloudDatabase
publicData.saveRecord(locationRecord) { record, error in
}
if error == nil
{
print("Location saved")
}
event1 = locations
}
This function fetches the locations from CloudKit:
func loadLocation()
{
let locations = [CKRecord]()
let publicData1 = CKContainer.defaultContainer().publicCloudDatabase
let query1 = CKQuery(recordType: "location", predicate: NSPredicate(format: "TRUEPREDICATE", argumentArray:nil))
publicData1.performQuery(query1, inZoneWithID: nil) { (results: [CKRecord]?, error: NSError?) -> Void in
if let locations = results
{
self.locations = locations
print(locations)
}
}
}
So to do this I made a unit test, that passes:
//
// CloudKitLocationsTests.swift
//
import XCTest
import UIKit
import CoreLocation
import CloudKit
class CloudKitLocationsTests: XCTestCase {
let locations = [ CLLocation(latitude: 34.4, longitude: -118.33), CLLocation(latitude: 32.2, longitude: -121.33) ]
func storeLocationToCloud(location:CLLocation) {
let locationRecord = CKRecord(recordType: "location")
locationRecord.setObject(location, forKey: "location")
let publicData = CKContainer.defaultContainer().publicCloudDatabase
publicData.saveRecord(locationRecord) { (records, error) in
if error != nil {
print("error saving locations: \(error)")
} else {
print("Locations saved: \(records)")
}
}
}
func fetchLocationsFromCloud(completion: (error:NSError?, records:[CKRecord]?) -> Void) {
let query = CKQuery(recordType: "Location", predicate: NSPredicate(value: true))
CKContainer.defaultContainer().publicCloudDatabase.performQuery(query, inZoneWithID: nil){
(records, error) in
if error != nil {
print("error fetching locations")
completion(error: error, records: nil)
} else {
print("found locations: \(records)")
completion(error: nil, records: records)
}
}
}
func testSavingLocations(){
let testExpectation = expectationWithDescription("saveLocations")
var n = 0
for location in self.locations {
let locationRecord = CKRecord(recordType: "Location")
locationRecord["location"] = location
let publicData = CKContainer.defaultContainer().publicCloudDatabase
publicData.saveRecord(locationRecord) { (records, error) in
if error != nil {
print("error saving locations: \(error)")
} else {
print("Locations saved: \(records)")
}
n += 1
if n >= self.locations.count {
testExpectation.fulfill()
}
}
}
// do something then call fulfill (in callback)
waitForExpectationsWithTimeout(10){ error in
if error != nil {
XCTFail("timed out waiting on expectation: \(testExpectation)")
}
}
}
func testFetchingLocations(){
let testExpectation = expectationWithDescription("FetchLocations")
fetchLocationsFromCloud(){ (error, records) in
if error != nil {
XCTFail("error fetching locations")
} else {
XCTAssertGreaterThan(records!.count, 0)
}
// do something then call fulfill (in callback)
testExpectation.fulfill()
}
waitForExpectationsWithTimeout(10){ error in
if error != nil {
XCTFail("timed out waiting on expectation: \(testExpectation)")
}
}
}
}
Note that you had case mismatch Location/location. Also, I am doing a subscript to set the field value.
Run this it works. Getting the location from the location manger callback has nothing to do with CloudKit so you should be able to plug this in as you require.
One other thing: I did turn on the option to allow you to query on ID field for the Location record type.
If your problem is to retrieve an array of CLLocation, try this:
publicData1.performQuery(query1, inZoneWithID: nil) { records, error in
var locations = [CLLocation]()
if let records = records {
for record in records {
if let location = record["location"] as? CLLocation {
locations.append(location)
}
}
}
}
I am trying to download multiple items from cloud kit but I'm getting the error "cannot assign type value (CKQueryCursor!, NSError) -> () to type (CKQueryCursor?, NSError?) -> void"
let locationToLookFor = CLLocation()
let predicate = NSPredicate(format: "location = %#", locationToLookFor as CLLocation)
let query = CKQuery(recordType: "Location", predicate: predicate)
let operation = CKQueryOperation(query: query)
operation.recordFetchedBlock = self.recordFetchBlock
operation.queryCompletionBlock =
{
[weak self]
(cursor: CKQueryCursor!, error: NSError) in
if(cursor != nil)
{
print("Fetching records")
let newOperation = CKQueryOperation(cursor: cursor)
operation.recordFetchedBlock = recordFetchBlock
operation.queryCompletionBlock = operation.queryCompletionBlock
self!.operationQueue.addOperation(newOperation)
}
else {
print("We have fetched all data")
}
}
operationQueue.addOperation(operation)
Your closure signature doesn't match the required signature. As shown in the error message, cursor should be optional as should error. You will also get an error because you don't unwrap cursor when you supply it to the new operation.
Try:
operation.queryCompletionBlock =
{
[weak self]
(cursor: CKQueryCursor?, error: NSError?) -> Void in
if let cursor = cursor
{
print("Fetching records")
let newOperation = CKQueryOperation(cursor: cursor)
operation.recordFetchedBlock = recordFetchBlock
operation.queryCompletionBlock = operation.queryCompletionBlock
self?.operationQueue.addOperation(newOperation)
}
else {
print("We have fetched all data")
}
}
I am using the code below to update a record. Once the record has been updated I would like to run my refresh function. At the moment the refresh function is sometimes called before the record has been updated so the refresh results are the same as before the record was updated.
Thanks
var tempDocumentsArray:NSArray!
let recordID = CKRecordID(recordName: "layerAbove")
var predicate = NSPredicate(format: "recordID = %#", recordID)
let query = CKQuery(recordType: "Layers", predicate: predicate)
self.publicDB.performQuery(query, inZoneWithID: nil) { (results, error) -> Void in
tempDocumentsArray = results
print("Results are: \(tempDocumentsArray)")
let record = tempDocumentsArray[0] as! CKRecord
var layerAbovePrevPos = record.objectForKey("layerNumber") as! Int
layerAbovePrevPos = layerAbovePrevPos - 1
let nlnChanged = record.setObject(layerAbovePrevPos, forKey: "layerNumber")
self.publicDB.saveRecord(record, completionHandler: { (returnRecord, error) -> Void in
if let err = error {
print("Error: \(err.localizedDescription)")
} else {
dispatch_async(dispatch_get_main_queue()) {
print("Success")
//TODO:This is sometimes called before the save is complete!
self.resetAndGet()
}
}
})
}