Random row in Parse - ios

I am creating an app that involves questions and facts and I need them to be selected at random. There is going to be a ton of different ones and I do not want to have to type in the ObjectId for each one of them. Is there a way to get the ObjectId of a random row so I don't have to write in the object Id for each question or fact that is in the class?

Three steps to accomplish what you want:
Know ahead of time or find out the number of questions you have to look through.
Create a random integer using arc4random() or some other method between 0 and that number.
Create a PFQuery on your question class with skip set to the random integer and limit set to 1.

Here is a function for retrieving your questions, only the first 1000, and saving them locally:
func saveAllObjectsLocally() {
let query = PFQuery(className: “Questions”)
query.limit = 1000
query.findObjectsInBackgroundWithBlock { (objects: [PFObject]?, error: NSError?) -> Void in
if error == nil {
if let objects = objects {
do {
try PFObject.pinAllInBackground(objects)
} catch let error as NSError? {
print("error \(error)")
}
}
} else {
print("Error: \(error!) \(error!.userInfo)")
}
}
}
Once they are saved locally, a random question can be selected:
func getRandomQuestion() -> PFObject? {
let query = PFQuery(className: “Questions”)
query.fromLocalDatastore()
query.findObjectsInBackgroundWithBlock {
(objects: [PFObject]?, error: NSError?) -> Void in
if let objects = objects {
let randomIndex = arc4random_uniform(UInt32(objects.count))
return objects[Int(randomIndex)]
}
}
return nil
}
This is going to be faster than accessing the network every time a random question is needed.
If you need to access the questions from the cloud you can add an index column to your Parse database and use that as a key to efficiently retrieve a random row.

Related

Query Parse for all the data in a table and placing into array

I am making a running app and would like to have a viewController in which the user has running tips and facts randomly display on the field. I would like to query parse for the objectIds to then reference the id and assign the label the tip or fact. Currently I have hard coded the Ids into the app but I would like that array to contain the results from the query the code is as follows:
func GetObjectID(){
ObjectIDs = ["id1","id2","id3","id4","id5","id6","id7","id8"]
RandomID = Int(arc4random_uniform(UInt32(ObjectIDs.count)))
}
func TheInfo(){
GetObjectID()
var query : PFQuery = PFQuery(className: "FactsandTips")
query.getObjectInBackgroundWithId(ObjectIDs[RandomID]){
(ObjectHolder : PFObject?, error: NSError?) -> Void in
if (error == nil) {
self.fact = ObjectHolder!.valueForKey("Fact") as? String
self.tips = ObjectHolder!.valueForKey("Tips") as? Array
if(self.tips.count > 0){
self.factLabel.text = self.fact
self.Button1.setTitle(self.tips[0], forState: UIControlState.Normal)
self.Button2.setTitle(self.tips[1], forState: UIControlState.Normal)
self.Button3.setTitle(self.tips[2], forState: UIControlState.Normal)
self.Button4.setTitle(self.tips[3], forState: UIControlState.Normal)
}
} else {
print("There is something wrong!")
}
}
}
I am using swift, Xcode7, and parse as my backend
Below is the code I use to query a Parse table, retrieve all results and add it all into an array. I then use the array as the source for a pickerView.
var query:PFQuery = PFQuery(className: "Drivers")
query.addAscendingOrder("Name")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
if let objects = objects as? [PFObject] {
for object in objects {
self.astrDrivers.append(object["Name"]! as! String)
}
}
self.pkvDriverTrack.reloadAllComponents()
} else {
print("Error: \(error) \(error!.userInfo)")
}
}
Note the line self.astrDrivers.append(object["Name"]! as! String). This is adding the Name column of each record to my self.astrDrivers array.
If you wanted to do retrieve multiple columns, your best bet is to create a custom object like below:
class ObjectNewFact:NSObject {
var column1:String = String() // You might want to choose more descriptive variable names (I just don't know what your column names are).
var column2:Int = Int()
// etc.
}
You could then create an array of ObjectNewFacts with a line like
var aFacts:[ObjectNewFact] = [ObjectNewFact]()
Then you could amend your routine to retrieve the data from Parse to:
var query:PFQuery = PFQuery(className: "Drivers")
query.addAscendingOrder("Name")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
if let objects = objects as? [PFObject] {
for object in objects {
var NewFact:ObjectNewFact = ObjectNewFact()
NewFact.column1 = object["Column1"] as! String
NewFact.column2 = object["Column2"] as! Int
self.aFacts.append(NewFact)
}
}
self.pkvDriverTrack.reloadAllComponents()
} else {
print("Error: \(error) \(error!.userInfo)")
}
}
Now you have an array full of facts. You might want to go down this custom object approach because you can also include things like the Fact ID or how many times the fact has been shown (if you're keeping track of that sort of thing). It provides a more flexible solution for any changes in the future.
I hope this helped.

Parse.com Query to Get All Items with a Specific Pointer

I want to get all items from my Parse.com table called Sticker, from a particular shop. My Sticker table has a column called shopId. So the obvious solution is this:
//get all stickers from one shop of category dress
var query = PFQuery(className:"Sticker")
query.whereKey("shopId", equalTo: "QjSbyC6k5C")
query.whereKey("category", equalTo: "DR")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
println("Successfully retrieved \(objects!.count) scores.")
// Do something with the found objects
if let objects = objects as? [PFObject] {
for object in objects {
println(object.objectId)
}
}
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
However that causes this error:
error: pointer field shopId needs a pointer value
I have seen a common solution for this seems to be to pass the query the actual object and not a string of the ID. Does this mean I have to first do a separate query to get the specific shop object, and then pass that to my query? Or is there a shorter way?
Here is my attempt to get the shop but it's causing this error:
Can only call -[PFObject init] on subclasses conforming to
PFSubclassing
var query1 = PFQuery(className: "Shop")
var shop1 = PFObject()
query1.getObjectInBackgroundWithId("QjSbyC6k5C") {
(shop: PFObject?, error: NSError?) -> Void in
shop1 = shop!
}
EDIT: So my solution was basically doing what the answer suggested. My code was this (Glamour is the name of the shop):
var shopQuery = PFQuery(className:"Shop")
shopQuery.getObjectInBackgroundWithId("QjSbyC6k5C") {
(glamour: PFObject?, error: NSError?) -> Void in
if error == nil && glamour != nil {
println(glamour)
//get all stickers from one shop of category dress
var query = PFQuery(className:"Sticker")
query.whereKey("shopId", equalTo: glamour!)
query.whereKey("category", equalTo: "DR")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
println("Successfully retrieved \(objects!.count) scores.")
// Do something with the found objects
if let objects = objects as? [PFObject] {
for object in objects {
println(object.objectId)
}
}
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
} else {
println(error)
}
}
I will leave this question here and maybe someone will answer with a comment: Is there any way to get the shop and give it class scope so that we do not have to nest the second query inside the success of the first query? Would that be more elegant?
You need to pass PFObject. change your code with following
PFObject *object = ...
var query = PFQuery(className:"Sticker")
query.whereKey("shopId", equalTo: "QjSbyC6k5C")
query.whereKey("category", equalTo: object);

Adding friends from address book using Parse

I'm building an app and at sign up/log in, it will go through the users address book, take the phone numbers, check against phone number column in the User class on Parse and if they aren't friends, then add them.
I have a function in AddressBookHelper.swift to help get the phone numbers:
class AddressBookHelper: NSObject {
let addressBook = AFAddressBookManager()
static var addressBookData = AFAddressBookManager.allContactsFromAddressBook()
static var contactsArray = [String]()
static func getContacts() -> [String] {
var array = [String]()
for contact in addressBookData{
let phoneNumberArray = contact.numbers as! [String]
for number in phoneNumberArray{
array.append(number)
}
}
return array
}
}
Then in ParseHelper.swift I do the check again Parse User class:
static func lookUpUserFromAddressBook(addressBook: [String], completionBlock: PFArrayResultBlock) {
for numbers in addressBook{
let query = User.query()
query!.whereKey("telephone", equalTo:numbers)
query!.findObjectsInBackgroundWithBlock(completionBlock)
}
}
And lastly, when the button is clicked, I add the user as a friend, if not already a friend:
#IBAction func importContacts(sender: AnyObject) {
ParseHelper.lookUpUserFromAddressBook(AddressBookHelper.getContacts()) {
(results: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
if let objects = results as? [PFObject] {
for object in objects {
let userObject = object as! User
self.matchedUsers.append(userObject)
let query = PFQuery(className: "Friends")
query.includeKey("toUser")
query.whereKey("fromUser", equalTo: User.currentUser()!)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
if (objects!.count == 0) {
ParseHelper.addFollowRelationshipFromUser(fromUser: User.currentUser()!, toUser: userObject, completionBlock: { (success, error) -> Void in
if success{
self.performSegueWithIdentifier("skipped", sender: self)
}
else{
print("Error: \(error!) \(error!.userInfo)")
}
})
}
else{
self.performSegueWithIdentifier("skipped", sender: self)
}
} else {
print("Error: \(error) \(error!.userInfo)")
}
}
}
}
}
else {
print("Error: \(error!) \(error!.userInfo)")
}
}
}
Am I doing this the right way? It works, but the code seems a bit long. Is there an easier way to do this?
Thanks in advance
You should take a look at PFQuery's whereKey:containedIn:. This will return you a list of all users whose phone numbers match any of the numbers you pass in an array. This will greatly reduce the number of queries you need to do to retrieve the user's contacts.
// Find users with any of these phone numbers
let numbers = ["1234567890", "1111111111", "222222222"]
query.whereKey("phoneNumber", containedIn: numbers)
From the Parse iOS Developers Guide:
https://parse.com/docs/ios/guide#queries
If you want to retrieve objects matching several different values, you
can use whereKey:containedIn:, providing an array of acceptable
values. This is often useful to replace multiple queries with a single
query.
You can also create all the follow relationships in one query using PFObject.saveAllInBackground() which takes an array of PFObjects to save.

How to put asynchronous Parse.com functions in a separate Class in Swift?

I am using Parse.com as my backend. I would like to put all functions/code related to accessing Parse.com in a single class that I can call from different ViewControllers.
Problem is that - since many of these functions from Parse.com are asynchronous, how does one return a value from these functions to update the UI?
For example, in the following function I am going and getting Earnings information of the current user. Since this function is using the asynchronous method findObjectsInBackgroundWithBlock from parse.com, I cannot really return anything from this function.
Is there a workaround to this problem? Currently, I am having to place this in function in the same ViewController class. (and hence having to repeat this same function in multiple viewControllers. Would like to have it in a single function only)
Only solution I see is to go to the synchronous method findObjects. Is there any other way?
func getcurrUserEarnings() {
/// Get details of currentUser from Earnings Class
///
/// :param - NSInterval
/// :returns - Int
func loadEarningsInfo() {
if (PFUser.currentUser() != nil) {
var query = PFQuery(className:"Earnings")
query.whereKey("user", equalTo: PFUser.currentUser()!)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
if let objects = objects as? [PFObject] {
for object in objects {
println(object.objectId)
//WANT TO UPDATE UI HERE WITH THE VALUES THAT WERE JUST RETURNED
}
}
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
}
}
}
You can use callback to pass in something.
For example:
func doSomething(callBack:(String)->())->(){
callBack("abc")
}
doSomething { (str:String) -> () in
println(str)
}
Also, do not forget to update UI on main thread
For example
func loadEarningsInfo(callBack:([PFObject])->()) {
if (PFUser.currentUser() != nil) {
var query = PFQuery(className:"Earnings")
query.whereKey("user", equalTo: PFUser.currentUser()!)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
if let objects = objects as? [PFObject] {
callBack(objects)
}
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
}
}
Then when you use
loadEarningsInfo { (objects:[PFObject]) -> () in
//Update UI with objects
}
You can also handle error in callback as well,I just post a simple example

How can i query parse data by creation date with swift?

I want to be able to organize the data i am receiving from parse by creation date, How do i do that?
I looked at the parse docs online, but i could not find anything that said how to query data by creation date. I also saw an answer of stack over flow, but it was in objective-c. Please answer in swift.
Here is the code i am using now to receive my data...
var query = PFQuery(className:"Class Name")
//this is what i tried..
query.orderByDescending("createdAt")
//
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
// Do something with the found objects
if let objects = objects as? [PFObject] {
for object in objects {
self.NameArray.insert(object.valueForKey("Name")! as! String, atIndex: 0)
self.TextArray.insert(object.valueForKey("Text")! as! String, atIndex: 0)
}
}
} else {
// Log details of the failure
self.alert("Error: \(error!.localizedDescription)", Message: "Make sure you have a secure internet connection")
}
dispatch_async(dispatch_get_main_queue()) {
println("Finished importing")
self.collectionView.reloadData()
}
}

Resources