Run code after a background process - parse.com - swift

I have a findObjectsInBackgroundWithBlock method in my viewController. Now i want to execute code, but just until this background method is finished. How can i do that?
I'm using the swift programming lanuage.

Here is some example code that could help you. It is not clear how you would restrict the code (PRE-BACKGROUND CODE) to run only while the background processing has completed. You may want to insert some code in the notification response function either to confirm that that PRE-BACKGROUND CODE is completed or to terminate it.
// ParseTestViewController.swift
import UIKit
import Foundation
import Parse
class ParseTestViewController: UIViewController {
var userData = [String]()
func codeToExecuteBeforeStringsAreAppended() {
}
func codeToExecuteAfterStringsAreAppended() {
// can use the array 'userData'
}
override func viewDidLoad() {
NSNotificationCenter.defaultCenter().addObserver(self,
selector: "notificationResponse:",
name: "recordsLoaded",
object: nil
)
self.getUserdataForUsername("MyName")
/* ==========================
Insert code tto be executed immediately after making the call that must not use the data returned by Parse. The function returns right away and the background may not have completed.
*/
codeToExecuteBeforeStringsAreAppended()
}
func getUserdataForUsername (queryUserName: String) {
var query = PFQuery(className:"UserData")
query.whereKey("username", equalTo: queryUserName)
let notification = NSNotification(name: "userDataRetrieved", object: self)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
for object in objects! {
if let username = object["username"] as? String {
self.userData.append (username)
}
}
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
NSNotificationCenter.defaultCenter().postNotification(notification)
}
}
func notificationResponse (notification: NSNotification) {
// this is executed after the background processing is done
// Insert the code that uses the data retrieved from Parse
codeToExecuteAfterStringsAreAppended()
NSNotificationCenter.defaultCenter().removeObserver(self)
}
}

This is pretty well covered in the documentation and guide on parse.com. But maybe you have a more specific question/scenario?
Guide to queries on parse.com
var query = PFQuery(className:"GameScore")
query.whereKey("playerName", equalTo:"Sean Plott")
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!)")
}
}
Edit: specific version for PFUser to array of usernames
var usernames: [String]?
func loadUsernames () {
if let query = PFUser.query() {
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil { // No error - should be good to go
self.userNames = (objects as! [PFUser]).map({$0.username!})
// Call/run any code you like here - remember 'self' keyword
// It will not run until userNames is populated
self.callSomeMethod()
} else { // Error - do something clever
}
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
loadUsernames()
}

Related

Swift - How to sort documents fetched from Firestore Database by date?

I have a tableViewController that gets loaded with documents fetched from Firestore Database. I would like to get them sorted by date and not ID, but I don't know where exactly to use sortby(). The documents already have a variable with the date, I am just not sure where I add the .sort(by: "date").
I already checked around, but most people have a very different code to populate their tableviews, and mine looks completely different.
Since I am new with Swift it took a lot of effort to get the tableViewController to work properly (mainly through online tutorials), but I don't fully understand it. This is the code:
This is an extension
class EncontradoService {
let database = Firestore.firestore()
func get(collectionID: String, handler: #escaping ([Encontrado]) -> Void) {
database.collection("EncontradosFinal")
.addSnapshotListener { querySnapshot, err in
if let error = err {
print(error)
handler([])
} else {
handler(Encontrado.build(from: querySnapshot?.documents ?? []))
}
}
}
}
And this is in the tableViewController
private var service: EncontradoService?
private var allencontrados = [Encontrado]() {
didSet {
DispatchQueue.main.async {
self.encontrados = self.allencontrados
}
}
}
var encontrados = [Encontrado]() {
didSet {
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
override func viewDidLoad()
{
super.viewDidLoad()
loadData()
}
func loadData() {
service = EncontradoService()
service?.get(collectionID: "EncontradosFinal") { encontrados in
self.allencontrados = encontrados
}
}
Thanks!

How to load UI with escaping closures and async calls?

I've written a function called 'configureLabels()' that is supposed to make a 'GET' request and retrieve a value which is then supposed to be set as the text for a label. The request is async so I thought I would be able to use an escaping closure to update the UI when the request is finished being made. I'm relatively new to coding, so I am not sure what I've done wrong. I'd really appreciate anyone's help in figuring this out.
This is the code containing the 'configureLabels()' method:
import UIKit
import SwiftyJSON
class ItemDetailViewController: UIViewController {
#IBOutlet weak var numberOfFiberGrams: UILabel!
var ndbnoResults = [JSON]()
var ndbno = ""
let requestManager = RequestManager()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
configureLabels()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func configureLabels() {
requestManager.ndbnoRequest(ndbno: ndbno) { (results) in
let json = JSON(results)
let fiber = json["food"]["nutrients"][7].dictionaryValue
for (key, value) in fiber {
if key == "value" {
self.numberOfFiberGrams.text = "\(value.stringValue)"
} else {
self.numberOfFiberGrams.text = "Fail"
}
}
}
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
And here is the code containing the function that 'configureLabels()' calls:
func ndbnoRequest(ndbno: String, apiKey: String, completionHandler: #escaping (_ results: JSON?) -> Void) {
Alamofire.request("https://api.nal.usda.gov/ndb/V2/reports?ndbno=\(ndbno)&type=f&format=json&api_key=\(apiKey)", method: .get).validate().responseJSON { response in
switch response.result {
case .success(let value):
let json = JSON(value)
completionHandler(json)
print("successful ndbno request")
case .failure(let error):
completionHandler(nil)
print(error)
}
}
}
Your code looks ok only issue I have find with your code is you are not calling the completionHandler in failure part, You need to always call completion block so it will gave you idea have you got response or not as of your completionHandler argument is type of [JSON] as of your not having response in failure part you are not calling completionHandler in it. What you can do is make it optional and call completionHandler with nil argument in case of failure.
func ndbnoRequest(ndbno: String, completionHandler: #escaping (_ results: [JSON]?) -> Void) {
let parameters = ["api_key": "tIgopGnvNSP7YJOQ17lGVwazeYI1TVhXNBA2Et9W", "format": "json", "ndbno": "\(ndbno)"]
Alamofire.request("https://api.nal.usda.gov/ndb/reports/V2", method: .get, parameters: parameters).responseJSON { response in
switch response.result {
case .success(let value):
let json = JSON(value)
let ndbnoResults = json["foods"].arrayValue
completionHandler(ndbnoResults)
print("successful ndbno request")
case .failure(let error):
completionHandler(nil)
print("error with ndbno request")
}
}
}
Now call it this way and wrapped the optional in completion block so you can confirm you get response.
requestManager.ndbnoRequest(ndbno: ndbno) { (results) in
if let result = results {
let json = JSON(result)
let fiber = json["food"]["nutrients"][7].dictionaryValue
for (key, value) in fiber {
if key == "value" {
self.numberOfFiberGrams.text = "\(value.stringValue)"
} else {
self.numberOfFiberGrams.text = "Fail"
}
}
}
else {
print("Problem to get response")
}
}
Everything related to UI must be ALWAYS done on the main thread.
So try this:
DispatchQueue.main.async {
let json = JSON(results)
let fiber = json["food"]["nutrients"][7].dictionaryValue
for (key, value) in fiber {
if key == "value" {
self.numberOfFiberGrams.text = "\(value.stringValue)"
} else {
self.numberOfFiberGrams.text = "Fail"
}
}
}
P.S. I agree with Nirav about failure callback - you should handle it too. And I strongly recommend you to give functions and vars more readable and meaningful names, not "ndbnoRequest" and "ndbno". You won't remember what does it mean in few weeks :)

How do I call a function only if another function is complete? (Swift and retrieving from Parse)

I am trying to only call a function only if I have retrieved a certain PFObjectfrom my Parse backend in a separate function. At the moment I am calling this second function after a set delay of 3.0 and it is working, but only if the first query function is called within 3.0, otherwise I have to pullToRefresh the tableView after the first function is eventually finished for it to populate with the Parse data. (I am using a PFQueryTableViewController by the way)
Here is my code at the moment (I am aware queryForTable is an override so is being called regardless, so is there a way to change this to a normal func?) :
override func viewDidLoad() {
// ideally want something like "if getX'IsComplete' then queryForTable() and self.loadObjects()"
retrieveFirstX()
let delay = 3.0 * Double(NSEC_PER_SEC) // retrieveFirstX must load within 3.0 for table to populate without needing to pullToRefresh
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue(), {
self.queryForTable()
self.loadObjects()
})
}
var X = PFObject(className: "X")
func retrieveFirstX() -> Bool {
let xQuery = PFQuery(className: "X")
xQuery.orderByDescending("createdAt")
xQuery.getFirstObjectInBackgroundWithBlock {
(object: PFObject?, error: NSError?) -> Void in
if error == nil {
self.X = object!
}
}
return true
}
override func queryForTable() -> PFQuery {
let xRelation = X.relationForKey("xPosts") as PFRelation!
let relationQuery = xRelation.query()
let xPostsQuery = PFQuery(className: "Posts")
xPostsQuery.includeKey("postUser")
xPostsQuery.whereKey("objectId", matchesKey: "objectId", inQuery: relationQuery)
xPostsQuery.cachePolicy = .NetworkElseCache
xPostsQuery.orderByDescending("createdAt")
return xPostsQuery
}
Do I need to use completion handlers, and if so how do I do that as I have never used them before?
Thanks in advance!
A completion handler sounds right, something like:
override func viewDidLoad() {
let completionHandler = {
self.queryForTable()
self.loadObjects()
}
retrieveFirstX(completionHandler)
}
func retrieveFirstX(completion: ()->()) -> Bool {
let xQuery = PFQuery(className: "X")
xQuery.orderByDescending("createdAt")
xQuery.getFirstObjectInBackgroundWithBlock {
(object: PFObject?, error: NSError?) -> Void in
if error == nil {
self.X = object!
completion()
}
}
return true
}

Ordering by object.count in PFQueryTableViewController

I need to query all objects in a class and order them all by the frequency of each object in Swift. I'm getting the query using...
// Define the query that will provide the data for the table view
override func queryForTable() -> PFQuery{
var query = PFQuery(className: "Upvotes")
return query
}
...but can't seem to retrieve an objects.count item since I can't use an async completion block with a findObjectsInBackground call. Is this something I should handle in the cellForRowAtIndexPath()?
But you can do something like this:
class demo: UIViewController {
var parseObjects: NSArray!
override func viewDidLoad() {
}
// Define the query that will provide the data for the table view
func yourQuery() -> PFQuery {
var query = PFQuery(className: "Upvotes")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
if (objects!.count > 0) {
parseObjects = objects //Store those objects in your variable
}
} else {
// Log details of the failure
println("Error: \(error) \(error!.userInfo!)")
}
}
}
}
and after that in your tableViewDelegate use parseObjects variable...

Parse.com Tried to save an object with a new, unsaved child with findObjectsInBackgroundWithBlock in Swift

Here are the definitions of entities:
class Event : PFObject, PFSubclassing {
override class func load() {
superclass()?.load()
self.registerSubclass()
}
class func parseClassName() -> String! {
return "Event"
}
}
˚
Now I am trying to retrieve list of teams belongs to Event:
var teams: [Team] = []
var query = PFQuery(className: "Team")
query.includeKey("event")
if (event != nil) {
query.whereKey("event", equalTo: event)
}
query.cachePolicy = kPFCachePolicyCacheThenNetwork
query.findObjectsInBackgroundWithBlock { (objects: [AnyObject]!, error:NSError!) -> Void in
I am getting error as,
[Error]: Caught "NSInternalInconsistencyException" with reason "Tried to save an object with a new, unsaved child.":
But if I remove the whereKey statement: query.whereKey("event", equalTo: event), the error disappears.
Any help would be appreciated.