How do you store a dictionary on Parse using swift? - swift

I am very new to swift and I don't know Obj C at all so many of the resources are hard to understand. Basically I'm trying to populate the dictionary with PFUsers from my query and then set PFUser["friends"] to this dictionary. Simply put I want a friends list in my PFUser class, where each friend is a PFUser and a string.
Thanks!
var user = PFUser()
var friendsPFUser:[PFUser] = []
var friendListDict: [PFUser:String] = Dictionary()
var query = PFUser.query()
query!.findObjectsInBackgroundWithBlock {
(users: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
println("Successfully retrieved \(users!.count) users.")
// Do something with the found objects
if let users = users as? [PFUser] {
friendsPFUser = users
for user in friendsPFUser{
friendListDict[user] = "confirmed"
}
user["friends"] = friendListDict //this line breaks things
user.saveInBackground()
}
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
To be clear, this code compiles but when I add
user["friends"] = friendListDict
my app crashes.

For those who might have this issues with. "NSInternalInconsistencyException" with reason "PFObject contains container item that isn't cached."
Adding Objects to a user (such as arrays or dictionaries) for security reasons on Parse, the user for such field that will be modified must be the current user.
Try signing up and using addObject inside the block and don't forget do save it!
It helped for a similar problem I had.

Related

Ambiguous reference to member 'save(_:completionHandler:)' with CloudKit save attempt

I'm trying to save back to CloudKit after updating a reference list and getting the error on the first line of this code block.
Error: Ambiguous reference to member 'save(_:completionHandler:)'
CKContainer.default().publicCloudDatabase.save(establishment) { [unowned self] record, error in
DispatchQueue.main.async {
if let error = error {
print("error handling to come")
} else {
print("success")
}
}
}
This sits within a function where the user going to follow a given location (Establishment). We're taking the existing establishment, and its record of followers, checking to see if the selected user is in it, and appending them to the list if not (or creating it if the list of followers is null).
Edit, in case helpful
//Both of these are passed in from the prior view controller
var establishment: Establishment?
var loggedInUserID: String?
#objc func addTapped() {
// in here, we want to take the logged in user's ID and append it to the list of people that want to follow this establishment
// which is a CK Record Reference
let userID = CKRecord.ID(recordName: loggedInUserID!)
var establishmentTemp: Establishment? = establishment
var followers: [CKRecord.Reference]? = establishmentTemp?.followers
let reference = CKRecord.Reference(recordID: userID, action: CKRecord_Reference_Action.none)
if followers != nil {
if !followers!.contains(reference) {
establishmentTemp?.followers?.append(reference)
}
} else {
followers = [reference]
establishmentTemp?.followers = followers
establishment = establishmentTemp
}
[this is where the CKContainer.default.....save block pasted at the top of the question comes in]
I've looked through the various posts on 'ambiguous reference' but haven't been able to figure out the source of my issue. tried to explicitly set the types for establisthmentTemp and followers in case that was the issue (based on the solutions to other related posts) but no luck.
Afraid I'm out of ideas as a relatively inexperienced newbie!
Help appreciated.
Documenting the solution that I figured out:
Combination of two issues:
I was trying to save an updated version of a CK Record instead of updating
I was not passing a CK Record to the save() call - but a custom object
(I believe point two was the cause of the 'ambiguous reference to member'
error)
I solved it by replacing the save attempt (first block of code in the question) with:
//first get the record ID for the current establishment that is to be updated
let establishmentRecordID = establishment?.id
//then fetch the item from CK
CKContainer.default().publicCloudDatabase.fetch(withRecordID: establishmentRecordID!) { updatedRecord, error in
if let error = error {
print("error handling to come")
} else {
//then update the 'people' array with the revised one
updatedRecord!.setObject(followers as __CKRecordObjCValue?, forKey: "people")
//then save it
CKContainer.default().publicCloudDatabase.save(updatedRecord!) { savedRecord, error in
}
}
}

Relational query on PFObject

I have a PFObject, Account that contains an array of Users which are subclasses of PFUserss. The User then has a NSDictonary property, allowableApps, that's a NSDictionary of arrays, where they arrays contain PFObjects.
So as a structure:
Account
var users: [User]
which points to....
User
// Each key is an array of AllowApp
var allowableApps: NSMutableDictionary
which points to...
AllowableApp
var appName: String
var appURL: String
var isAllowed: Bool
I'm trying to fetch all of these relations down to AllowableApp in a single query. I've tried using the .includeKey like this:
accountQuery?.includeKey("users")
accountQuery?.includeKey("allowableApps")
which didn't work. I've also tried:
accountQuery?.includeKey("users.allowableApps.appName")
accountQuery?.includeKey("users.allowableApps.appURL")
accountQuery?.includeKey("users.allowableApps.isAllowed")
I try to populate a UITableView with all the AllowableApp objects but I get this error:
Key "appName" has no data. Call fetchIfNeeded before getting its value.
Which I understand, I need to fetch all of them before trying to access the appName property. (which I'm trying to set cellForRowAtIndexPath).
Here is my full query:
let currentUser = User.currentUser()
let accountQuery = Account.query()
accountQuery?.whereKey("primaryUser", equalTo: currentUser!)
accountQuery?.includeKey("users.allowableApps")
accountQuery?.getFirstObjectInBackgroundWithBlock({ (account, error) in
if (error != nil) {
completion(users: nil, error: error)
}
else {
let users = (account as? Account)!.users
completion(users: users, error: nil)
}
})
My thought right now is to just loop through all of the AllowableApp objects in viewDidAppear calling fetchInBackgroundWithBlock. Then once they are all loaded I reload the table data.
This seems realllly messy and a common problem. Is there a more elegant solution that I'm just not seeing?
From what i understand you have the following structure:
Account
Users (Array of User)
AllowsableApps (Array of AllowApps)
First of all change the NSMutableDictionary to Array. NSMutableDictionary is a key-value pairs and in parse you should create one field. So you can use Array of AllowApps and it will do the same effect.
In order to fetch all accounts and users in each of the account and allowable apps per user you need to build the following query:
// You can do it with sub classing if you want
let query = PFQuery(className: "Account")
query.includeKey("users.allowableApps")
query.findObjectsInBackgroundWithBlock {
(objects: [PFObject]?, error: NSError?) -> Void in
}
Now for your users array. If your users array is users that needs to login the app it's better to inherit from PFUser and not from PFObject because PFUser contains all the logic for handling users in your app.

parse.com swift return values from class after query

I have a second class in parse where each row is linked to the user and stores an int number. This is already setup.
Now I want to check for the current user and get the int that is saved and put that in a label. Any Ideas?
you will need to query with constraints ("meaning that get data only for the currentUser"). Then you have two options to retrieve their data either use getfirstobjectinbackgroundwithblock() method which will get at least one row of data from parse. Or use findObjectsInBackgroundWithBlock() method which will return multiple rows from parse.
if in parse, the user is being saved as pointer use that line:
let userT = PFUser.CurrentUser() //<-- to get CurrentUser
if in parse, the user is being saved as string use that line
let userT = PFUser.CurrentUser().username //<-- to get CurrentUser
In this code, I use the getFirstObjectInBackgroundWithBlock method because I think you are only displaying one thing for the user, So if I am wrong use the second method findObjectsInBackgroundWithBlock
let query = PFQuery(className:"whateverNameYourClassIs")
let userT = PFUser.CurrentUser() //<-- to get CurrentUser
query.whereKey("NameOfUserColummInParse" equalTo:userT!)
query.getFirstObjectInBackgroundWithBlock { (object: PFObject?, error: NSError?) -> Void in
if error == nil
{
if let retreiveObject = object
{
let data = retreiveObject["IntValue"] as! Int //<-- IntValue supposed to be the name of your class column in parse where you want to retrieve the value.
}
}
})

How to properly Pin in Parse localDataStore?

I have Parse.enableLocalDatastore() in my app delegate before Parse.setApplicationId
Then I have var newPosts = PFObject(className: "Post") as a global variable.
Then I want to get 1,000 latest objects from the "Post" table from localDataStore that I enabled earlier so I do this:
var getNewPosts = PFQuery(className: "Post")
getNewPosts.fromLocalDatastore()
getNewPosts.limit = 1000
getNewPosts.orderByDescending("createdAt")
getNewPosts.findObjectsInBackgroundWithBlock {
(downloadedPosts, error) -> Void in
if downloadedPosts != nil && error == nil {
println(downloadedPosts) // I am getting table with no data
}
}
But I only get empty data rows.
If I comment out the getNewPosts.fromLocalDatastore() line results are fine.
I understand that I am missing the critical Pinning step but not sure from Parse documentation hoe and where to implement it. Can you please help?
You are getting no data....
reasons my be...
Wrong name of class (class names are case sensitive)
Data is not there in local storage
Try synchronous version of findObject: method and pin: method.

How to access custom user table columns from Parse.com

How should I access columns that I've added to the User table when I have a currentUser object?
I have a PFUser.currentUser() and I want to access the nickname column that I added via the web interface.
Can I use the currentUser to get the data e.g.:
var nickname = PFUser.currentUser()["nickname"] as String
Or do I have to use a user query? e.g.:
var query = PFUser.query()
query.whereKey("username", equalTo:PFUser.currentUser().username)
var user = query.findObjects().first as PFUser
var nickname = user["nickname"]
If you added date to the column locally, then you have to use the first way as you wrote, or if you added date in the browser, or uploaded to parse.com some way, you have to use the second way.
I would like to give my two cents too. First of all, Daniel was right in saying that if you added the date in the browser or uploaded it to parse.com, you need to use the second way. This is an updated answer with iOS 9 and Xcode 7.2:
var query = PFUser.query()
query!.whereKey("username", equalTo:PFUser.currentUser()!.username!)
do {
user = try query!.findObjects().first as! PFUser
} catch {
print("Error finding user")
}
if user?["rankNumber"] as? Int == nil {
user!["rankNumber"] = 0
user!.saveInBackground()
} else {
print(user!["rankNumber"] as! Int)
}
If I did it any other way, xcode would give me an error saying "failing to unwrap optional". I hope this can help someone!