This question already has an answer here:
Return UIImage Array From Parse Query Block
(1 answer)
Closed 7 years ago.
I have the following method which primarily uses the username parameter to load the user's picture from the backend Parse.
func getProfilePicture(username: String) -> UIImage
{
var tempImage:UIImage = UIImage(named: "sample-qr-code.png")!
let query: PFQuery = PFQuery(className: "_User")
query.whereKey("appUsername", equalTo: username)
query.findObjectsInBackgroundWithBlock {
(objects:[PFObject]?, error:NSError?) -> Void in
for object in objects! {
let imageFiles = object["ProfilePic"] as! PFFile
imageFiles.getDataInBackgroundWithBlock({
(imageData: NSData?, error: NSError?) -> Void in
if (error == nil) {
tempImage = UIImage(data:imageData!)!
}
})
}
}
return tempImage
}
In the first line of the method, I am assigning a value to tempImage for the simple reason that it can't be empty otherwise it throws nil exception. When the code above runs, it goes through all the stages and conditions (detected by logging using print function) but the returned image is still sample-qr-code.png instead of the one in the database. There are no errors thrown from backend and this line gets executed:
tempImage = UIImage(data:imageData!)!
Can someone help me solve why this is not showing picture from backend? I am guessing it could be something to do with initialization and scope, but not certain.
This is happening because the function completes before the tasks within the closures do.
You should make tempImage a property of self and have the closures update that property.
class SomeClass {
var tempImage: UIImage?
func someFuncToGetStuff() {
Parse.doParseStuffWithCompletion(fetchedPFFile) -> Void in
let myImage = UIImage(data: fetchedPFFile)
self.tempImage = myImage
}
}
}
query.findObjectsInBackgroundWithBlock is running in the background. That means when your function returns, it will have the default image that you originally assigned to it.
And tempImage can be reset or assiged immediately, or up to a second after the getProfilePicture method returns the default image.
You need to redo how you are getting that image, e.g. get it synchronously (i.e. via findObjects:, potentially slow) or pass in an image view outlet and have the image written directly to the image view as soon as the background function completes successfully.
Related
I try to do a query with Parse containing a few Strings and Images that will be added to an array. The strings in the array are all in the right order but not the images. I think its probably because some images are smaller than the other ones and so they get appended to the array earlier than they are supposed to. Is there any way to "save" space in the array for the images to keep them in the right order? It's probably not that hard to solve that but I am a Newbie :( Thank you!
query.findObjectsInBackground (block: { (objects:[PFObject]?, error: Error?) -> Void in
for object in objects! {
DispatchQueue.global(qos: .userInteractive).async {
// Async background process
if let imageFile : PFFile = self.bild.append(object.value(forKey: "Bild") as! PFFile) {
imageFile.getDataInBackground(block: { (data, error) in
if error == nil {
DispatchQueue.main.async {
// Async main thread
let image = UIImage(data: data!)
image2.append(image!)
}
} else {
print(error!.localizedDescription)
}
})
}
}
}
})
Your analysis is correct that the requests will complete in non-deterministic order, partly or mostly influenced by the amount of data that must be returned.
Instead of an array to which you append the UIImage (or data), use a mutable dictionary that maps strings to UIImage. A reasonable choice for the string key is the PFFile name.
EDIT I'm not a Swift writer, but I tried to express the idea below (don't depend on it compiling, but I think the idea is sound)
class MyClass {
var objects: [PFObject] = []
var images: [String: UIImage] = [:] // we'll map names to images
fetchObjects() {
// form the query
query.findObjectsInBackground (block: { (objects:[PFObject]?, error: Error?) -> Void in
self.objects = objects
self.fetchImages()
})
}
fetchImages() {
for object in self.objects! {
if let imageFile : PFFile = object["Bild"] as PFFile {
self.fetchImage(imageFile);
}
}
}
fetchImage(imageFile: PFFile) {
imageFile.getDataInBackground(block: { (data, error) in
if error == nil {
self.images[imageFile.name] = UIImage(data: data!)
// we can do more here: update the UI that with image that has arrived
// determine if we're done by comparing the count of images to the count of objects
} else {
// handle error
}
}
}
}
This will get the images in the background and keep them associated with their filenames using a dictionary. The OP code didn't explain what self.bild is, but I assumed it was an instance array of retrieved PFFiles. I replaced this with the images instance var.
Image file order is maintained by the object collection: to get the Nth image, get the Nth object, get it's "Bild" property, that PFFile's name is the key into your images dictionary.
var n = // some index into objects
var object : PFObject = self.objects[n]
var file : PFFile = object["Bild"]
var name : String = file.name
var nthImage = self.images[name] // is nil before fetch is complete
I am struggling to return a UIImage from a Parse Function.
I'm fairly new to return functions but I have got Parse and well now I am trying to make a global function which I can reference in MVVM schema instead of continuously repeating the code throughout my app.
The function works perfectly without the return statement added (-> UIImage), I just can't get a UIImage then.
func fetchImage(imageFile : PFFile) -> UIImage
{
var image : UIImage!
imageFile.getDataInBackground { (data, error) in
if error == nil
{
image = UIImage(data: data!)
}
else
{
print(error!)
image = default-img
}
}
return image
}
I'm sure it's a simple error I'm making, I just can't seem to find anything about adding a return statement to a parse function.
I am creating an app for test taking in Swift, and I'm using Parse to handle the backend. There are two main "object" types that I am working with in Parse: Test and Question. Each Test object contains an array of Question objects called "questions". I need to capture the questions array of the test object with the getObjectInBackgroundWithId method, as I have the test's objectId value, and save it to an array I have declared earlier in the method. When I assign the array to my questions array from the beginning of the method inside of the closure, I print it, and it appears to have been correctly copied, but when I print it outside of the closure, it hasn't been copied. Here is the method:
#IBAction func endTestPressed(sender: UIButton)
{
let lab = self.pinLabel.text!
var questions = [PFObject]()
let query = PFQuery(className:"Test")
query.getObjectInBackgroundWithId(lab.substringFromIndex(advance(lab.startIndex,5)))
{
(test: PFObject?, error: NSError?) -> Void in
if error == nil && test != nil
{
questions = test?["questions"] as! [PFObject]
print("Inside of closure: \(questions)")
}
else
{
print(error)
}
}
print("Outside of closure: \(questions)")
}
How can I save the array from Parse as an array declared in the method before the closure?
It is not that the array is empty in the outside the closure, what happens is that getObjectInBackgroundWithId happens in the background, the rest of your app still running, so you first print the outside println command, than when the results come back from the background thread it just run the completion block
#IBAction func endTestPressed(sender: UIButton)
{
let lab = self.pinLabel.text!
var questions = [PFObject]()
let query = PFQuery(className:"Test")
query.getObjectInBackgroundWithId(lab.substringFromIndex(advance(lab.startIndex,5)))
{
//Run when the getObjectInBackgroundWithId return with the results
(test: PFObject?, error: NSError?) -> Void in
if error == nil && test != nil
{
questions = test?["questions"] as! [PFObject]
print("Inside of closure: \(questions)") //this happen after the print outside the closure as the request happens in the backgroun
}
else
{
print(error)
}
}
//Application continue run while the getObjectInBackgroundWithId retrives data from Parse.com
print("Outside of closure: \(questions)") //This will happen first and the array is not populate yet
}
I've subclassed PFUser in my iOS app and I'm using this function to grab the profile picture. profilePicture is the #NSManaged PFFile and profilePictureImage is a UIImage.
This works great except for the fact that getData() and fetchIfNeeded() are potential long running operations on the main thread.
Can anyone think of a good way to implement this method so the scary parts run on a background thread?
Thanks!
func image() -> UIImage!
{
if !(self.profilePictureImage != nil)
{
if self.profilePicture != nil
{
self.fetchIfNeeded()
if let data = self.profilePicture!.getData() {
self.profilePictureImage = UIImage(data: data)
return self.profilePictureImage
}
}else {
return UIImage(named: "no_photo")!
}
}
return self.profilePictureImage
}
Change the method so that rather than returning an image it takes a closure which is called when the image is available and passes it as a parameter. This may be called immediately or after some delay if the image needs to be downloaded.
Just do it as you say, run the task in the background using: fetchIfNeededInBackgroundWithBlock. Also, your image function should look something like this:
func imageInBackgroundWithBlock(block: ((UIImage?, NSError?) -> Void)?) {
var image: UIImage?
self.fetchIfNeededInBackgroundWithBlock({ (user, error) -> Void in
if error == nil {
// load the picture here
image = ...
} else {
println(error!.userInfo)
image = UIImage(named: "no_photo")!
}
// return after fetching the user and the image
block?(image, error)
})
}
Array returning a blank array when outside of the PFQuery. For some reason, the items are not being passed to the array when compiled.
class DriverViewController: UIViewController {
var placesArr : Array<Place> = []
override func viewDidLoad() {
super.viewDidLoad()
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
var query = PFQuery(className:"places")
query.whereKey("username", equalTo:"email#email.com")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
println("Successfully retrieved \(objects!.count) scores.")
if let objects = objects as? [PFObject] {
for object in objects {
let x = Place(aIdent: (object["Ident"] as! Int), aName: (object["name"] as! String), aAddress: (object["originAddress"] as! String), aCity: (object["originCity"] as! String), aCategoryName: (object["catName"] as! String), aLat: (object["aLat"] as! String), aLng: (object["aLng"] as! String))
self.placesArr.append(x)
println(placesArr) //****It works here and prints an array****
}
}
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
println(placesArr) //****But here it returns a blank array and this is where I need it to return an array****
This a very common misunderstanding relating to threading, the issue is what order events run:
// Runs 1st
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
// Runs 3rd
}
// Runs 2nd
println(placesArr)
The execution of the program doesn't halt when you call findObjectsInBackground, it finds objects: inBackground which means the heavy lifting of a network request is dispatched to a different queue so that the user can still interact with the screen. A simple way to do this would be to do:
var placesArray: [Place] = [] {
didSet {
// Do any execution that needs to wait for places array here.
}
}
You can also trigger subsequent actions within the parse response block, I just personally find executing behavior dependent on a property update being executed in didSet to be a nice way to control flow.
Logan's answer is right in the money.
See my answer in this thread: Storing values in completionHandlers - Swift
I wrote up a detailed description of how async completion handlers work, and in the comments there is a link to a working example project the illustrates it.
query.findObjectsInBackgroundWithBlock is a block operation that performs in the background - it's asynchronous.
The line println(placesArr) actually executes before the block is finished - that's why you see nil there.