How to check if a record is on 2 different tables? - swift

I'm using Parse.com and looking for an efficient way of handling this problem.
I have a Sales table which has a User reference column and an Offer reference column.
The Sales table keeps track of which users came in to claim an offer (i.e. 10% discount).
So I have the following subclasses
Offers
Users
Sales
I want to display all available offers but set a local hasBeenUsed flag so that I can display the Offers differently if they have already been used.
At the moment I can create a local array of available Offers but I'm not sure how to set this hasBeenUsed flag accordingly.
var query = Message.query()
query.findObjectsInBackgroundWithBlock { (objects: [AnyObject]!, error: NSError!) -> Void in
for object in objects {
var offer = Offer()
offer.info = object["info"] as String
if (also on the Sales list)
offer.hasBeenUsed = true
}
self.offers.insert(offer, atIndex: 0)
}
}

Caveat: Please pardon any Swift errors I make. I am still writing exclusively in Objective-C and I've tried my best to write good Swift, but I am not yet an expert in the syntax.
The best answer I can come up with is that you'll need 2 queries (and this assumes you care about Sales for [PFUser currentUser] or PFUser.currentUser()):
Get the list of Sales objects for the current user
Get the list of all (current) Offer objects.
Query 1
Swift Version
var query = PFQuery(className:"Sales")
query.includeKey("Offer")
query.whereKey("User", equalTo:PFUser.currentUser())
query.findObjectsInBackgroundWithBlock { (objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
// Assign the objects to a property or something
self.sales = objects
// **Then**, run query 2
runQuery2()
} else {
// Do error handling...
}
}
Objective-C Version
PFQuery *query = [PFQuery queryWithClassName:#"Sales"];
[query includeKey:#"Offer"];
[query whereKey:#"User" equalTo:[PFUser currentUser]];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (error != nil) {
// Do error handling
} else {
// Assign the objects to a property or something...
self.sales = objects;
// **Then**, run query 2
[self runQuery2];
}
}
];
Query 2
Swift Version
var query = PFQuery(className:"Offers")
// Whatever query constraints you need to get "current" offers
query.findObjectsInBackgroundWithBlock { (objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
// Assign the objects to a property or something
self.offers = objects;
// At this point, you have all the data you need, so call the "filter" method
filter()
} else {
// Do error handling...
}
}
Objective-C Version
PFQuery *query = [PFQuery queryWithClassName:#"Offers"];
// Whatever query constraints you need to get "current" offers
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (error != nil) {
// Do error handling
} else {
// Assign the objects to a property or something
self.offers = objects;
// At this point, you have all the data you need, so call the "filter" method
[self filter];
}
}
];
Filter Method
This assumes 2 properties: "sales", an array of sales for the current user, and "offers", an array of all current offers
Swift Version
// First, get all of the "redeemed" offers into an array
var localOffers = [PFObject]()
for sale in self.sales {
localOffers.append(sale.offer)
// It might be easier to do it this way
// localOffers.append(sale.offer.objectId)
}
// Next, iterate the "all offers" array and mark each offer as redeemed/not redeemed
for offer in self.offers {
if (localOffers.containsObject(offer)) {
// Alternative check
// if (localOffers.containsObject(offer.objectId)) {
offer.hasBeenUsed = true
} else {
offer.hasBeenUser = false
}
}
Objective-C Version
// First, get all of the "redeemed" offers into an array
NSMutableArray *localOffers = [NSMutableArray array];
for (PFObject *sale in self.sales) {
[localOffers addObject:sale.offer];
// It might be easier to do it this way
// [localOffers addObject:self.offer.objectId];
}
// Next, iterate the "all offers" array and mark each offer as redeemed/not redeemed
for (PFObject *offer in self.offers) {
if ([localOffers containsObject:offer]) {
// Alternative check
// if ([localOffers containsObject:offer.objectId]) {
offer[#"hasBeenUsed"] = #(YES);
} else {
offer[#"hasBeenUsed"] = #(NO);
}
}
At this point, you've got what you need to display the data differently based upon whether the offer has been redeemed or not. Just be careful not to save the hasBeenUsed values to Parse unless that's what you intend.

Related

Is it possible to create an Array of specific Objects in Parse?

I have made a QR scanner App, I have manually put some QR codes into parse for it to recognise, any QR codes scanned that I haven't put into parse don't get recognised.
The only thing to tell them apart is their (Info) i.e "restaurant", "nail salon" etc.
I am after a way to be able to record an Integer of how many times the chosen QRCode has been scanned, to then place on a label in the app.
I can (.count) ALL of the qrCodes saved and scanned by the user but can't seem to figure out how I can then either put all "Nail Salons" into their own array on parse or run a For loop matching the ones I need.
// The code below will retrieve everything in the "info" column and print it to console
// This prints "Nails Salon" x 5, "Restaurant" x3 and "Coffee Shop" x 7 in the order that they were scanned (Unorganised)
// What block of code could I make to display what PFuser.current currently has in their parse?
// E.g. PFUser has scanned "Nail Salon" 5 Times, "Restaurant" 3 time etc etc
let infoCheck = PFQuery(className: "UserQRCodes")
infoCheck.whereKey("info", contains: "")
infoCheck.findObjectsInBackground { (objects: [PFObject]?, error: Error?) in
if let error = error {
print(error.localizedDescription)
} else if let objects = objects {
print(objects)
}
}
// To retrieve everything the USER has scanned and display it as String on the APP
let query = PFQuery(className: "UserQRCodes")
query.whereKey("userName", equalTo: PFUser.current()!)
query.findObjectsInBackground { (objects: [PFObject]?, error: Error?) in
if let error = error {
//log details of the failure
print(error.localizedDescription)
} else if let objects = objects {
let stampees: Int = objects.count
let totalStampees = String(stampees)
self.stampeesCollectedLabel.text = totalStampees
print(objects.count)
}
}
// Do any additional setup after loading the view.
}
You want to filter elements in your array of scans. For each code type, call something like
// '$0' is your PFObject. Replace 'name' with whatever `PFObject` property
// represents the object's type
let nailSalons = objects.filter { $0.name == "Nail Salon" }
You can then use this filtered array to get your count.
Note that the filter { $0... } syntax is a shorthand for
objects.filter { (object) throws -> Bool) in
return object.name == "Nail Salon"
}
You'll need to use the full version if your condition is anything more complicated than a simple one-line expression. Note that in the short version, the return is implied.

Fetching reminders from calendars in Swift

What's the thread-safe way of fetching Reminders from various calendars? I'm just trying to count all reminders and print them. The printing works but the counting doesn't. Is there a race-condition because fetching reminders is asynchronous?
func loadFromCalendars(cals: [EKCalendar], completed: (NSError?)->()) {
// STEP 1 OF CREATING AN OVERALL COMPLETION BLOCK: Create a dispatch group.
let loadCalendarServiceGroup: dispatch_group_t = dispatch_group_create()
// Define errors to be processed when everything is complete.
// One error per service; in this example we'll have two
let configError: NSError? = nil
let preferenceError: NSError? = nil
var reminderCounter = 0
let eventStore : EKEventStore = EKEventStore()
eventStore.requestAccessToEntityType(EKEntityType.Event, completion: {
granted, error in
if (granted) && (error == nil) {
print("granted \(granted)")
print("error \(error)")
}
})
// Go through calendars.
for cal in cals {
let remindersPredicate = eventStore.predicateForRemindersInCalendars([cal])
// STEP 2 OF CREATING AN OVERALL COMPLETION BLOCK: Adding tasks to a dispatch group
dispatch_group_enter(loadCalendarServiceGroup)
eventStore.fetchRemindersMatchingPredicate(remindersPredicate) {
// MARK: Begininning of thread
reminders in
_ = (reminders!).map {
// TRYING TO COUNT HERE THE REMINDERS. ALWAYS PRINTS 0!
reminder -> EKReminder in
print(reminder.title)
reminderCounter += 1
return reminder
}
dispatch_async(dispatch_get_main_queue()) {
self.sendChangedNotification() // refreshes the UI
}
}
// STEP 3 OF CREATING AN OVERALL COMPLETION BLOCK: Leave dispatch group. This must be done at the end of the completion block.
dispatch_group_leave(loadCalendarServiceGroup)
// MARK: End of thread
}
// STEP 4 OF CREATING AN OVERALL COMPLETION BLOCK: Acting when the group is finished
dispatch_group_notify(loadCalendarServiceGroup, dispatch_get_main_queue(), {
print("************ reminder count: \(reminderCounter) ****************")
// Assess any errors
var overallError: NSError? = nil;
if configError != nil || preferenceError != nil {
// Either make a new error or assign one of them to the overall error. Use '??', which is the "nil Coalescing Operator". It's syntactic sugar for the longer expression:
// overallError = configError != nil ? configError : preferenceError
overallError = configError ?? preferenceError
} // Now call the final completion block
// Call the completed function passed to loadCalendarHelper. This will contain the stuff that I want executed in the end.
completed(overallError)
})
}
EDIT
Thanks for the great tips, jtbandes! I simplified my code (a lot!) One question - I'm chaining some functions that change the resulting data structure. How can I make in the below code groupArrayBy() thread-safe?
public extension SequenceType {
/// Categorises elements of self into a dictionary, with the keys given by keyFunc
func groupArrayBy<U : Hashable>(#noescape keyFunc: Generator.Element -> U) -> [U:[Generator.Element]] {
var dict: [U:[Generator.Element]] = [:]
for el in self {
let key = keyFunc(el)
if case nil = dict[key]?.append(el) { dict[key] = [el] }
}
return dict
}
}
func loadFromCalendars(cals: [EKCalendar], completed: (NSError?)->()) {
let configError: NSError? = nil
let preferenceError: NSError? = nil
withEstore { // retrieves the EKEventStore
estore in
let predicate = estore.predicateForRemindersInCalendars(cals)
estore.fetchRemindersMatchingPredicate(predicate) { reminders in
print("Number of reminders: \(reminders?.count ?? 0)") // Prints correct result
let list = (reminders!).map {
// this map still works, it seems thread-safe
reminder -> ReminderWrapper in
return ReminderWrapper(reminder: reminder) // This still works. ReminderWrapper is just a wrapper class. Not very interesting...
}.groupArrayBy { $0.reminder.calendar } // ERROR: groupArrayBy doesn't seem to be thread-safe!
print("Number of reminders: \(Array(list.values).count)") // Prints a too low count. Proves that groupArrayBy isn't thread-safe.
dispatch_async(dispatch_get_main_queue()) {
self.sendChangedNotification() // refreshes the UI
completed(configError ?? preferenceError)
}
}
}
}
A few changes should be made to this code:
dispatch_group_leave(loadCalendarServiceGroup) must be inside the fetchRemindersMatchingPredicate block. Otherwise, the block you passed to dispatch_group_notify will execute before the fetches are finished, which entirely defeats the purpose of using a group.
The requestAccessToEntityType call is also asynchronous, but your code simply continues after starting the access request, without waiting for it to finish. You might want to chain your completion blocks together.
You're requesting access to the .Event type, but you probably want .Reminder.
reminderCounter += 1 is not thread-safe. You might want to dispatch_async onto a serial queue before changing the counter (so there is no contention between threads), or you could use the OSAtomicAdd family of functions.
Instead of _ = (reminders!).map { reminder in ... }, I would recommend you just use for reminder in reminders { ... }.
However…
I think what you're doing is overly complicated.
Notice that predicateForRemindersInCalendars takes an array of calendars. You can simply pass all your calendars, cals, to get a single predicate encompassing all of them, and run a single query:
let predicate = eventStore.predicateForRemindersInCalendars(cals)
eventStore.fetchRemindersMatchingPredicate(predicate) { reminders in
print("Number of reminders: \(reminders?.count ?? 0)")
dispatch_async(dispatch_get_main_queue()) {
self.sendChangedNotification() // refreshes the UI
completed(configError ?? preferenceError)
}
}

PFQuery categorizing results into multilevel arrays

I am trying to do a multiple PFQuery and storing the data properly.
My plan is to query for group members in my Group
I am looking for the key member. It gives me an array of objectIds. Then I want to query in my _User class for their picture with key firstImage. Displaying the data is not the problem, just getting it the right way.
The data needs to be stored considering it's groups. This is what i tried so far:
let fetchGroupsQuery = PFQuery(className: "Group")
fetchGroupsQuery.whereKey("member", equalTo: objectID!)
fetchGroupsQuery.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if error != nil
{
}
else {
if let objects = objects {
self.memberPhotos.removeAll()
for object in objects {
if let member = object["member"] as? [String]
{
// returns array of objectIds
for user in member
{
// returns single ids
self.groupIds.append(user)
let photoQuery = PFUser.query()
photoQuery?.whereKey("objectId", equalTo: user)
photoQuery?.findObjectsInBackgroundWithBlock({ (results, error) -> Void in
if error != nil {
// ....
}
else {
self.memberPhotos.removeAll()
if let results = results
{
for result in results
{
if result["firstImage"] != nil
{
self.memberPhotos.append(result["firstImage"])
}
}
}
}})
}
}
My idea was to store the data within an array of arrays.
So that I can later go into array[indexPath.row] to get the array I need to loop through in order to get the right group pictures.
Any ideas how to solve this?
I would use two separate queries function:
1) to get their ID's
2) for their pictures.
For the first query: what you need to change?
Your objectID array should be of type of NSMutableArray because in order to fetch their info you should use the constraint whereKey containedIn not whereKey equalTo
and for that constraint you should downcast your NSMutableArray to type AnyObject.
Also change the Type of your groupIds to NSMutableArray, then append your array.
For the Second query:
Since your groupIds Type is already NSMutableArray just cast it to AnyObject,then use the whereKey containedIn
Note: It would be better to download all images before appending your
array. So you have the option of using struct or class(easier to
group data to its owner) then create an array of that data
structure.Where it because easier to populate your UI.

How to check if background queries have been completed in Parse.com with Swift

I need to populate an array with different users' data retrieved from Parse.com. Using getFirstObject is apparently not a good idea, but I don't know how to find out when all data have been fetched for every user:
myObjects = []
for user in users { // users is an array of PFUsers
let query = PFQuery(className: "MyClass")
query.whereKey("asdh74tbf", equalTo: user)
query.getFirstObjectInBackgroundWithBlock{ (object: PFObject!, error: NSError!) -> Void in
if let object = object {
self.myObjects.append(object)
} else {
// handle error
}
}
}
I want to perform self.tableView.reloadData(), when all objects have been retrieved. How to do this?
Use dispatch groups. Check here in the "Waiting on Groups of Queued Tasks" for more information.
Also you mentioned you want to get all objects of every user. So instead of getFirstObject you can use findObjectsInBackgroundWithBlock method. The block returns the parsed response. So combining this and the dispatch groups your code can look something like this
//create a new dispatch group
let group: dispatch_group_t = dispatch_group_create();
var myObjects = []
for user in users { // users is an array of PFUsers
let query = PFQuery(className: "MyClass")
query.whereKey("asdh74tbf", equalTo: user)
//enter the dispatch group
dispatch_group_enter(group);
query.findObjectsInBackgroundWithBlock{ (objects: NSArray!, error: NSError!) -> Void in
if let object = object {
self.myObjects.append(object)
} else {
// handle error
}
//leave the dispatch group
dispatch_group_leave(group);
}
}
//will be called when all the async operations entered in the group are finished.
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
self.tableView.reloadData()
});

The method does not enter for loop Parse Swift

I use parse to query current user's friend list and the friend request user and when user press each cell of the friend request, The app will add that friend back and delete the selected friend request so I query friend list and friend request and use "addedArray" as friend requests and "duplicate" as array of current user's friend list and use for loop to find the duplicate of friend list and friend request and delete that friend from addedArray so the current user will se the latest friend requests
Here's my code in swift
func queryAdded(){
let query = PFQuery(className: "Request")
let user = PFUser.currentUser()?.relationForKey("Friends")
let query2 = user?.query()
query.whereKey("To", equalTo: PFUser.currentUser()!)
query.findObjectsInBackgroundWithBlock {
(objects, error) -> Void in
if error == nil{
for object in objects! {
print("query")
let username = object.valueForKey("FromUsername") as! String
self.userCellAdded = username
self.addedArray.append(username)
print(username)
print(self.addedArray.count)
}
print("READY")
print(self.addedArray.count)
self.tableView.reloadData()
}
else{
/* dispatch_async(dispatch_get_main_queue()){
//reload the table view
query.cachePolicy = PFCachePolicy.NetworkElseCache
}*/
print("errorrrr")
}
}
query2!.findObjectsInBackgroundWithBlock{(objects,error) -> Void in
if error == nil {
for object in (objects)!{
if let username = object["username"] as? String {
self.duplicate.append(username)
print("duplicate")
print(username)
print("size")
print(self.duplicate.count)
}
}
}
}
for self.iIndex = 0 ; self.iIndex < self.addedArray.count ; ++self.iIndex {
for self.jIndex = 0 ; self.jIndex < self.duplicate.count ; ++self.jIndex {
print("in for loop")
if self.addedArray[self.iIndex] == self.duplicate[self.jIndex] {
self.addedArray.removeAtIndex(self.iIndex)
self.tableView.reloadData()
print("find")
}
}
}
}
The problem is The method queryAdded() does not run for loop for me and I don't understand why
The duplicate array and the addedArray have value and size but still it didn't go inside the for loop
Your problem is that your for loop is depending on the results of two asynchronous operations. What happens is that your app starts these two background queries and then immediately starts the for loop. Since there is no data yet from the queries, the for loop has no data to work on.
You can either solve this by creating a "pyramid hell" by nesting your operations (bad), or you can use a framework to achieve the same as Promises would provide for JavaScript (good).
Since you're using Parse, you have such a framework already; namely the Bolts Framework. You could then perform these operations sequentially using tasks (BFTask).
Example from the Bolts readme:
var query = PFQuery(className:"Student")
query.orderByDescending("gpa")
findAsync(query).continueWithSuccessBlock {
(task: BFTask!) -> BFTask in
let students = task.result() as NSArray
var valedictorian = students.objectAtIndex(0) as PFObject
valedictorian["valedictorian"] = true
return self.saveAsync(valedictorian)
}.continueWithSuccessBlock {
(task: BFTask!) -> BFTask in
var valedictorian = task.result() as PFObject
return self.findAsync(query)
}.continueWithSuccessBlock {
(task: BFTask!) -> BFTask in
let students = task.result() as NSArray
var salutatorian = students.objectAtIndex(1) as PFObject
salutatorian["salutatorian"] = true
return self.saveAsync(salutatorian)
}.continueWithSuccessBlock {
(task: BFTask!) -> AnyObject! in
// Everything is done!
return nil
}
You could then first prepare both your queries and then start the chain of tasks:
query1.findObjectsInBackground().continueWithSuccessBlock {
(task: BFTask!) -> BFTask in
var objects = task.result() as NSArray
for object in objects {
//collect your usernames
}
return query2.findObjectsInBackground()
}.continueWithSuccessBlock {
(task: BFTask!) -> AnyObject! in
var objects = task.result() as NSArray
for object in objects {
// collect your usernames from relation
}
// Call a function containing the for loop that is currently not running
return nil
}
The for loop is run
duplicate array and the addedArray have value and size - No they don't
findObjectsInBackgroundWithBlock runs the query in ... the background.
Therefore your program does the following:
start the first query
start the second query
run the for loop
the queries finish at some arbitrary point in time.
In particular when the program reaches point 3 the arrays do not contain anything, they are empty arrays, therefore the for-loop executes perfectly fine as it is supposed to be: it does nothing since there is nothing to loop over.
Solution:
Move the for loop into a function that you call after the first query and the second query finish.