App hang when I try add new object to PFRelation - ios

I'm trying to add one more object to PFRelation. I think I've check most common issue and object which I'm trying to add is already saved at parse. Also, relation is pointing to the same type of class in parse as type of object which I'm trying to add.
Code
func tapToJoin() {
dispatch_async(queue) { () -> Void in
if let user = User.localUsername() {
joinToEvent(user, toEventWithId: eventId) { (success) in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
if success {
SVProgressHUD.showSuccessWithStatus("Joined")
} else {
SVProgressHUD.showErrorWithStatus("Error")
}
})
}
}
}
}
func joinToEvent(user:User,toEventWithId eventId:String, complete:(Bool)->Void){
do {
//1. query event
let eventQuery = PFQuery(className: "Event", predicate: NSPredicate(format: "objectId = %#",eventId))
let event = try eventQuery.findObjects().first
//2. find user
let userQuery = PFQuery(className: "User", predicate: NSPredicate(format: "objectId = %#",user.id))
let parseUser = try userQuery.findObjects().first!
//3. add user to event.participants
let eventParticipants = event?.objectForKey("participants") as! PFRelation
eventParticipants.addObject(parseUser)
//4. save event to parse
try event?.save()
complete(true)
} catch {
complete(false)
}
}
Problem
App hang at eventParticipants.addObject(parseUser). I'm not sure how I should approach this issue.

At least one problem is that eventParticipants should be initialized with event.relationForKey, not objectForKey

Related

Updating an entity record instead of creating a new record with CoreData using Swift

I need to fetch some information from the server.
and then I need to save them using CoreData.
The problem is that the same record with same attributes is saved several times.
I want to save just new records to the CoreData and update the existing ones.
func saveChanges() throws {
var error: ErrorType?
mainQueueContext.performBlockAndWait () {
if self.mainQueueContext.hasChanges {
do {
try self.mainQueueContext.save()
} catch let saveError {
error = saveError
}
}
}
if let error = error {
throw error
}
}
func fetchRecentPosts(completion completion: (PostResult) -> Void){
.
.
.
do{
try self.coreDataStack.saveChanges()
print("now data is saved in this device")
}
catch let error {
result = .Failure(error)
}
.
.
.
}
My idea is to check if that id was saved. If it was saved before I should not add another record. Then how can I update the existing one?
And is it a good way to solve this problem?
First check if your record exists in DB
static func getMyRecord(with id : String, context : NSManagedObjectContext) -> MyRecord? {
let fetchRequest: NSFetchRequest< MyRecord> = MyRecord.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "id = %#", id)
let foundRecordsArray = try! context.fetch(fetchRequest)
if foundRecordsArray.count > 0 {
return foundRecordsArray[0]
}
return nil
}
While inserting if available update it else create new record.
static func insertRecord(_ record : MyRecord, context : NSManagaedObjectContext) {
let foundRecord = DataInserterClass. getMyRecord(with: yourID, context: context)
if foundRecord == nil {
//create new record
}
else {
//update foundRecord
}
try! context.save()
}

loop in a loop not working

I'm working on an app for school project.
After accessing the user's contacts, I want to loop through and show only the contacts who are also users of the app. *Their username is their mobile number.
below are 3 functions.
the first one getAppUsers() works fine.
the third one getDisplayedUser() does not work. and i wonder why
the second one getUserContacts() works. but it is only there to check which part of my loop isn't working. :/
so apparently my loop in a loop has something wrong which i can't figure out (it didn't even get to the "you're HERE"). please help me out. THANKS!
var appUsers = [String]()
var contactStore = CNContactStore()
var userContacts = [CNContact]()
var displayedContacts = [name: phoneNumber]()
func getAppUsers() {
let appUsersQuery = PFUser.query()
appUsersQuery?.findObjectsInBackground { (objects, error) in
if error != nil {
print("WTF")
} else if let users = objects {
for object in users {
print("FYEAH!")
if let user = object as? PFUser {
self.appUsers.append(user.username!)
print(user.username)
}
}
}
}
}
func getUserContacts() {
for b in userContacts {
let c = (b.phoneNumbers[0].value).stringValue
let d = c.replacingOccurrences(of: "\\D", with: "", options: .regularExpression, range: c.startIndex..<c.endIndex)
print("you got here")
print(d)
}
}
func getDisplayedUser() {
for a in appUsers {
for b in userContacts {
let c = (b.phoneNumbers[0].value).stringValue
let d = c.replacingOccurrences(of: "\\D", with: "", options: .regularExpression, range: c.startIndex..<c.endIndex)
print("you're HERE")
print(d)
if a == d {
print("FOUND IT")
print(b.givenName + " " + b.familyName)
}
}
}
}
The getDisplayedUser should be call after the loop finished in in getAppUsers because it is executing in asynchronous mode. I added the row after finished loop below
func getAppUsers() {
let appUsersQuery = PFUser.query()
appUsersQuery?.findObjectsInBackground { (objects, error) in
if error != nil {
print("WTF")
} else if let users = objects {
for object in users {
print("FYEAH!")
if let user = object as? PFUser {
self.appUsers.append(user.username!)
print(user.username)
}
}
// Call it here please ..
self.getDisplayedUser()
}
}
}

Parse not updating the core data Swift

I am trying to make changes to the data already stored in the core data in Parse. But it is not making the necessary changes. And I looked at the documentation for parse regarding objects here: https://parse.com/docs/ios/guide#objects. And it seems that I am doing exactly what the document is telling me to do. Maybe I am missing something? Here is my code:
import UIKit
import Parse
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var products = PFObject(className:"Products")
products["name"] = "ice cream"
products["description"] = "Strawberry"
products["price"] = 4.99
products.saveInBackgroundWithBlock {
(success, error) -> Void in
if (success) {
println("Object saved with id \(products.objectId)")
} else {
println("Not successful!")
print(error)
}
}
//ignore the code below this line for now please :)
var query = PFQuery(className: "Products")
query.getObjectInBackgroundWithId("7lmnxHxibK", block: { (object: PFObject?, error) -> Void in
if error != nil {
print(error)
} else if let product = object {
print("YO")
product["description"] = "Rocky Road"
product["price"] = 5.99
products.saveInBackground()
}
})
}
}
So the code above created an object with the ID 7lmnxHxibK. The description being Strawberry, the name being ice cream, and the price being 4.99. Which worked as it should. So now as an attempt to change the attributes in the object with the ID 7lmnxHxibK, I wrote the following code:
var query = PFQuery(className: "Products")
query.getObjectInBackgroundWithId("7lmnxHxibK", block: { (object: PFObject?, error) -> Void in
if error != nil {
print(error)
} else if let product = object {
print("YO")
product["description"] = "Rocky Road"
product["price"] = 5.99
products.saveInBackground()
}
})
This code should make the necessary changes to the object with the id 7lmnxHxibK. But rather than making the necessary changes to the object's attributes, it is creating a new object with it's description, name, and price all being (undefined). Anybody have a solution to fix this?
You have to change products.saveInBackground() to product.saveInBackground(). Also by the time you call your second query parse may not be done saving the object for the first time.

Error with Parse: PFObject does not have a member named 'createdAt'

I am stuck on that issue. I've read the Parse documentation (https://parse.com/docs/ios/guide#queries-query-constraints) but it doesn't helps me that much.
The error
When I try to get "createdAt" or "updatedAt" from a PFObject, I got the following error:
['PFObject'] does not have a member named 'createdAt'
The Code
Here is the (shortened) function:
func loadCheckInData() {
var query = PFQuery(className: "CheckIn")
query.orderByDescending("createdAt")
query.selectKeys(["firstName","lastName","updatedAt","createdAt"])
query.findObjectsInBackgroundWithBlock({
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
println(objects)
// Do something
if let object = objects as? [PFObject] {
println("\(object.createdAt)") <---- Error here
self.CheckedPatients = Array(object.generate())
}
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
})
}
Then I retrieve "firstName", "lastName" (and try to retrieve "createdAt") that are in my "CheckIn" Parse's class with the following code
func collectionView(cellView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
// Add parse data
if let value = CheckedPatients[indexPath.row]["lastName"] as? String {
cell.cellLabelTwo.text = value
}
if let value = CheckedPatients[indexPath.row]["firstName"] as? String {
cell.cellLabel.text = value
}
if let value = CheckedPatients[indexPath.row]["createdAt"] as? String {
cell.cellDay.text = value
}
return cell
}
And I call the function
override func viewDidAppear(animated: Bool) {
loadCheckInData()
}
In fact I tried differents method to get that "createdAt" value, and I can't make it works. Does anyone have an idea (and a quick explanation if possible) it could be nice.
Thank you !
It's simply because updatedAt/createdAt is a property on all PFObjects, no need to retrieve it using a key, just treat it as property.
I figured out how to get the property, thanks to #SanitLee answer. I post my code with the solution so that everybody could see it.
Solution
In my function named loadCheckInData() I added for object in objects { to the findObjectsInBackgroundWithBlock method. See below:
func loadCheckInData() {
var query = PFQuery(className: "CheckIn")
query.orderByDescending("createdAt")
query.findObjectsInBackgroundWithBlock({
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// Do something
if let objects = objects as? [PFObject] {
for object in objects { // <--- Added
// data stored in the var named "CheckedPatient"
self.CheckedPatients = Array(objects.generate())
}
}
} else {
// ...
}
})
}
Then to retrieve the createdAt property inside the cellForItemAtIndexPath function, I changed:
if let value = CheckedPatients[indexPath.row]["createdAt"] as? String {
cell.cellDay.text = value
}
into:
var DateVar : PFObject = CheckedPatients[indexPath.row]
var dateFormatter:NSDateFormatter = NSDateFormatter() // Formating
dateFormatter.dateFormat = "EEEE dd MMM HH:mm"
cell.cellDay.text = dateFormatter.stringFromDate(DateVar.createdAt!)
Now it works as I want. If you have any advice, thorough explanations or improvement about the code, feel free to modify and explain it.
Thanks

CloudKit: Fetch all records with a certain record type?

I have currently got CloudKit set up in my app so that I am adding a new record using the help of the following code below,
CKRecordID *recordID = [[CKRecordID alloc] initWithRecordName:#"stringArray"];
CKRecord *record = [[CKRecord alloc] initWithRecordType:#"Strings" recordID:recordID];
[record setObject:[NSArray arrayWithObjects:#"one", #"two", #"three", #"four", nil] forKey:#"stringArray"];
[_privateDatabase saveRecord:record completionHandler:nil];
However, now I would like to be able to fetch ALL records that are of the same record type, "Strings," and return those compiled into an NSArray. How would I go about doing that? Currently, all I have figured out is how to fetch each record individually, using a recordID, which is a hassle, there must be an easier way.
[_privateDatabase fetchRecordWithID:recordID completionHandler:^(CKRecord *record, NSError *error) {
if (error) {
// Error handling for failed fetch from private database
}
else {
NSLog(#"ICLOUD TEST: %#", [record objectForKey:#"stringArray"]);
}
}];
Aaaand, I've got it. Using the code below, I was able to create a query to run on the database, to then return an NSArray in the completion block, which I looped through, and returned the value for the saved key in an NSLog.
NSPredicate *predicate = [NSPredicate predicateWithValue:YES];
CKQuery *query = [[CKQuery alloc] initWithRecordType:#"Strings" predicate:predicate];
[_privateDatabase performQuery:query inZoneWithID:nil completionHandler:^(NSArray *results, NSError *error) {
for (CKRecord *record in results) {
NSLog(#"Contents: %#", [record objectForKey:#"stringArray"]);
}
}];
Solution for Swift 4,
shows how to fetch all the records of type "YourTable", also prints System Field and Custom Field:
let query = CKQuery(recordType: "YourTable", predicate: NSPredicate(value: true))
CKContainer.default().publicCloudDatabase.perform(query, inZoneWith: nil) { (records, error) in
records?.forEach({ (record) in
// System Field from property
let recordName_fromProperty = record.recordID.recordName
print("System Field, recordName: \(recordName_fromProperty)")
// Custom Field from key path (eg: deeplink)
let deeplink = record.value(forKey: "deeplink")
print("Custom Field, deeplink: \(deeplink ?? "")")
})
}
Swift 5
After looking through a bunch of posts and solutions on SO I have managed to come with a solution that suits my needs and should be simple enough for anyone that just wants to fetch all of their records of given type from iCloud.
Solution
The solution that uses an extension to the CKDatabase to introduce a method that handles the cursor: CKQueryOperation.Cursor of CKQueryOperation to continue asking iCloud for more records. In this approach I dispatch to the background queue so I can block it and wait for the operation to be finished completely, either on receiving an error or with the last batch of records. Once the semaphore unlocks the queue it proceeds with calling the main completion block with the result. Also I am taking advantage of Swift's Result type in the completion handler.
extension CKDatabase {
func fetchAll(
recordType: String, resultsLimit: Int = 100, timeout: TimeInterval = 60,
completion: #escaping (Result<[CKRecord], Error>) -> Void
) {
DispatchQueue.global().async { [unowned self] in
let query = CKQuery(
recordType: recordType, predicate: NSPredicate(value: true)
)
let semaphore = DispatchSemaphore(value: 0)
var records = [CKRecord]()
var error: Error?
var operation = CKQueryOperation(query: query)
operation.resultsLimit = resultsLimit
operation.recordFetchedBlock = { records.append($0) }
operation.queryCompletionBlock = { (cursor, err) in
guard err == nil, let cursor = cursor else {
error = err
semaphore.signal()
return
}
let newOperation = CKQueryOperation(cursor: cursor)
newOperation.resultsLimit = operation.resultsLimit
newOperation.recordFetchedBlock = operation.recordFetchedBlock
newOperation.queryCompletionBlock = operation.queryCompletionBlock
operation = newOperation
self?.add(newOperation)
}
self?.add(operation)
_ = semaphore.wait(timeout: .now() + 60)
if let error = error {
completion(.failure(error))
} else {
completion(.success(records))
}
}
}
}
Usage
Using the method is fairly straight forward for anyone familiar with Swift's closure syntax and Result type.
let database: CKDatabase = ...
database.fetchAll(recordType: "User") { result in
switch result {
case .success(let users):
// Handle fetched users, ex. save them to the database
case .failure(let error):
// Handle Error
}
}
}
Here's the answer in Swift 3.0.
func myQuery() {
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "tableName", predicate: predicate)
publicDatabase.perform(query, inZoneWith: nil) { (record, error) in
for record: CKRecord in record! {
//...
// if you want to access a certain 'field'.
let name = record.value(forKeyPath: "Name") as! String
}
}
}
The followig function will return ALL records for requested record type:
let database = CKContainer(identifier: "container_here").privateCloudDatabase
typealias RecordsErrorHandler = ([CKRecord], Swift.Error?) -> Void
func fetchRecords(forType type: String, completion: RecordsErrorHandler? = nil) {
var records = [CKRecord]()
let query = CKQuery(recordType: type, predicate: NSPredicate(value: true))
let queryOperation = CKQueryOperation(query: query)
queryOperation.zoneID = CloudAssistant.shared.zone.zoneID
queryOperation.recordFetchedBlock = { record in
records.append(record)
}
queryOperation.queryCompletionBlock = { cursor, error in
self.fetchRecords(with: cursor, error: error, records: records) { records in
completion?(records, nil)
}
}
database.add(queryOperation)
}
private func fetchRecords(with cursor: CKQueryCursor?, error: Swift.Error?, records: [CKRecord], completion: RecordsHandler?) {
var currentRecords = records
if let cursor = cursor, error == nil {
let queryOperation = CKQueryOperation(cursor: cursor)
queryOperation.recordFetchedBlock = { record in
currentRecords.append(record)
}
queryOperation.queryCompletionBlock = { cursor, error in
self.fetchRecords(with: cursor, error: error, records: currentRecords, completion: completion)
}
database.add(queryOperation)
} else {
completion?(records)
}
}
In trying to fetch all records, and understand the structure and details of Cloudkit storage, I found it useful to have the following function available during debug. This uses semaphores to retain the data structure for printing. There may be a more elegant way to do this but this works!
//
// Print a list of records in all zones in all databases
//
func printRecordsInContainers() {
let myContainer = CKContainer.default()
// Edit the following dictionary to include any known containers and possible record types
let containerRecordTypes: [CKContainer: [String]] = [ myContainer: ["MyRecordType", "OldRecordType", "MyUser", "PrivateInfo"] ]
let containers = Array(containerRecordTypes.keys)
for containerz in containers {
let databases: [CKDatabase] = [containerz.publicCloudDatabase, containerz.privateCloudDatabase, containerz.sharedCloudDatabase]
for database in databases {
var dbType = "<None>"
if database.databaseScope.rawValue == 1 { dbType = "Public" }
if database.databaseScope.rawValue == 2 { dbType = "Private" }
if database.databaseScope.rawValue == 3 { dbType = "Shared" }
//print("\(database.debugDescription)")
print("\n\n\n🥨 ---------------------------------------------------------------------------------------------------")
print("🥨 ----- Container: \(containerz.containerIdentifier ?? "??") ----- Database: \(dbType)")
let semaphore1 = DispatchSemaphore(value: 0) // Initiate semaphore1 to wait for closure to return
database.fetchAllRecordZones { zones, error in
if let error = error {
print("🧨 Error Fetching Zones: \(error.localizedDescription)")
}
else if let zones = zones {
print("~~~~ \(zones.count) : \(zones)")
for zone in zones {
print("----- Zone ID: \(zone.zoneID)\n")
for recordType in containerRecordTypes[container] ?? [] {
print("[ Record Type: \(recordType.description) ]")
let query = CKQuery(recordType: recordType, predicate: NSPredicate(value: true))
let semaphore = DispatchSemaphore(value: 0) // Initiate semaphore to wait for closure to return
database.perform(query, inZoneWith: zone.zoneID) { records, error in
if let error = error {
print("🧨 Error in Record Query: \(error.localizedDescription)")
}
else if let records = records {
printRecordDescriptions(records)
}
semaphore.signal() // Send semaphore signal to indicate closure is complete
}
semaphore.wait() // Wait for semaphore to indicate that closure is complete
}
}
}
else {
print("🧨 Error in fetchAllRecordZones")
}
semaphore1.signal() // Send semaphore1 signal to indicate closure is complete
}
semaphore1.wait() // Wait for semaphore1 to indicate that closure is complete
}
}
}
class func printRecordDescriptions(_ records: [CKRecord]) {
print("Records and Contents List:")
for record in records {
print("🧰 Record: \(record.recordID)")
for key in record.allKeys() {
print(" Key - \(key)")
}
}
print("Record List End\n")
}

Resources