My question: what is the best way to know when the last image has been downloaded, since once all files have been downloaded I need to update the UI.
I am downloading an array of PFFiles from Parse. What I am doing the moment is: getting the array of PFFiles from parse this way
func getAllUserPictures(completion: (imageFiles: [PFFile]) -> Void) {
guard let id = currentUserID else {
return
}
let query = PFQuery(className: "Profile")
query.whereKey("objectId", equalTo: id)
query.includeKey("avatars")
query.findObjectsInBackgroundWithBlock { profile, error in
guard let imageFiles = profile?.first?.objectForKey("avatars") as? [PFFile] else {
return
}
completion(imageFiles: imageFiles)
}
}
After having obtained the imageFile array from the completion handler I do proceed to download them this way:
func getUserPicturesWithBlock(completion: (result: [UIImage]) -> Void) {
DataManager.sharedInstance.getAllUserPictures { imageFiles in
for file in imageFiles {
file.getDataInBackgroundWithBlock({ data, error in
guard let data = data, let image = UIImage (data: data) else{
return
}
self.imagesArray.append(image)
})
}
}
}
Have you considered using multiple PFImageView from the ParseUI framework?
You could assign each of the PFFiles to a PFImageView in the completion of the query and then call loadInBackground on each PFImageView. The PFImageView class automatically handles updating the images once the data has been loaded and this would also update each image as soon as it's fetched rather than waiting for all of them together.
Alternatively, to keep a similar structure to what you already have, you could use the size of the imageFiles array to tell when they have all been loaded. In the completion of loading each file, you could check if the number of loaded images is equal to the total size of the array.
Related
I didn't find an answer that satisfied me and hope you have any idea. I want to upload my images to the Firebase storage and save the imageUrls into the Firebase database.
var imageUrls = [String]()
func uploadImagesToStorage(imagesArray: [UIImage]) {
for i in imagesArray {
guard let uploadData = UIImageJPEGRepresentation(i, 0.3) else { return }
let fileName = NSUUID().uuidString
FIRStorage.storage().reference().child("post_Images").child(fileName).put(uploadData, metadata: nil) { (metadata, err) in
if let err = err {
return
}
guard let profileImageUrl = metadata?.downloadURL()?.absoluteString else { return }
self.imageUrls.append(profileImageUrl)
}.resume()
} //..End loop
saveToDatabaseWithImageUrl(imageUrls: imageUrls)
Uploading the images works with the uploadImagesToStorage(imagesArray: [UIImage]) method. This method gets an array as argument which contains the images that I want to upload. While uploading the images I'm downloading the imageUrl information from the metadata that firebase is giving me and save that imageUrl into the imageUrls array. For loop is necessary to save the imageUrl information for every single image. When the images are uploaded and the imageUrls Array is filled with the imageUrl information I call the function func saveToDatabaseWithImageUrl(imageUrls: [String]) to save the imageUrls into the database. Checking Firebase I see that the images are saved into the Firebase storage, but the imageUrls are not saved into the Firebase database. While debugging my code I found out that the reason for this behavior is that while the images are uploaded the code jumps to the next line. So it calls the saveToDatabaseWithImageUrls with an empty imageUrls array. I read the [Documentation][1] and tried to manage the upload with the .resume() method. Still it jumped to the saveToDatabaseWithImageUrl method. I don't know how to guarantee that the upload is finished and then the next line of code is executed. Thanks for your help.
Its happen because success block of your .child("post_Images").child(fileName).put call asynchronously. Rest of code go sync. So your for start uploading photos and after that you are saving URLs to database but urls are empty because you don't wait for finish uploading.
I give you a perfect solution based on DispathGroup
//Create DispatchGroup
let fetchGroup = DispatchGroup()
for i in imagesArray {
guard let uploadData = UIImageJPEGRepresentation(i, 0.3) else { return }
let fileName = NSUUID().uuidString
//Before every iteration enter to group
fetchGroup.enter()
FIRStorage.storage().reference().child("post_Images").child(fileName).put(uploadData, metadata: nil) { (metadata, err) in
if let err = err {
fetchGroup.leave()
return
}
guard let profileImageUrl = metadata?.downloadURL()?.absoluteString else { return }
self.imageUrls.append(profileImageUrl)
//after every completion asynchronously task leave the group
fetchGroup.leave()
}.resume()
}
And know id magic
fetchGroup.notify(queue: DispatchQueue.main) {
//this code will call when number of enter of group will be equal to number of leaves from group
//save your url here
saveToDatabaseWithImageUrl(imageUrls: imageUrls)
}
This solution don't block a thread, everything works asynchronously here.
I am new to CloudKit.
I would like to know how I can retrieve from the database only one image. I created a new record type and added a Asset type in it with the name EventsImage. Now I want to retrieve this image and place it in an ImageView I have on my View controller. This is an image of what the View looks like (it is not a tableViewController).
(There is an ImageView in the middle)
Here is a snippet in which I recover an image from my database. I fetch CKRecord using the record ID.
self.publicDatabase.fetch(withRecordID: recordID, completionHandler: { (record: CKRecord?, error: Error?) -> (Void) in
guard let record = record else
{
if let error = error
{
DispatchQueue.main.async
{
completionHandler(SeeYuuResult.error(message: error.localizedDescription))
}
}
return
}
// Here is where the image is recovered
if let asset = record["avatar"] as? CKAsset, let data = try? Data(contentsOf: asset.fileURL)
{
DispatchQueue.main.async
{
avatar_image = UIImage(data: data)
}
}
})
i'm trying to upload or download images using Nuke(framework for downloading and Caching images) And Firebase to upload images as the backend
for single file it's easy to deal with without any problem
but for multiple ones i don't really know what to do right
i'm having an issues where it don't do it job synchronously
it downloads second image before the first one sometimes
i'll show my way of downloading and uploading multiple images
For download i put the code in for-loop
func downloadImages(completion: (result: [ImageSource]) -> Void){
var images = [ImageSource]()
for i in 0...imageURLs.count-1{
let request = ImageRequest(URL: NSURL(string:imageURLs[i])!)
Nuke.taskWith(request) { response in
if response.isSuccess{
let image = ImageSource(image: response.image!)
images.append(image)
if i == self.imageURLs.count-1 {
completion(result: images)
}
}
}.resume()
}
}
And for uploading where the user chooses the images
form image picker and return it as NSData array
And then perform this code
func uploadImages(completion: (result: [String]) -> Void){
let storageRef = storage.referenceForURL("gs://project-xxxxxxxxx.appspot.com/Uploads/\(ref.childByAutoId())")
var imageUrl = [String]()
var imgNum = 0
for i in 0...imageData.count-1 {
let imagesRef = storageRef.child("\(FIRAuth.auth()?.currentUser?.uid) \(imgNum)")
imgNum+=1
let uploadTask = imagesRef.putData(imageData[i], metadata: nil) { metadata, error in
if (error != nil) {
print("error")
imageUrl = [String]()
completion(result: imageUrl)
} else {
print("uploading")
// Metadata contains file metadata such as size, content-type, and download URL.
let downloadURL = metadata!.downloadURL()?.absoluteString
print(downloadURL)
imageUrl.append(downloadURL!)
if i == imageUrl.count-1{ //end of the loop
print("completionUpload")
completion(result: imageUrl)
}
}
}}
this is good way to do this task ?
what should i do to make each image downloads in order ?
please give me anything that may help example code , link etc ..
Thanks in advance
We highly recommend using Firebase Storage and the Firebase Realtime Database together to accomplish lists of downloads:
Shared:
// Firebase services
var database: FIRDatabase!
var storage: FIRStorage!
...
// Initialize Database, Auth, Storage
database = FIRDatabase.database()
storage = FIRStorage.storage()
Upload:
let fileData = NSData() // get data...
let storageRef = storage.reference().child("myFiles/myFile")
storageRef.putData(fileData).observeStatus(.Success) { (snapshot) in
// When the image has successfully uploaded, we get it's download URL
let downloadURL = snapshot.metadata?.downloadURL()?.absoluteString
// Write the download URL to the Realtime Database
let dbRef = database.reference().child("myFiles/myFile")
dbRef.setValue(downloadURL)
}
Download:
let dbRef = database.reference().child("myFiles")
dbRef.observeEventType(.ChildAdded, withBlock: { (snapshot) in
// Get download URL from snapshot
let downloadURL = snapshot.value() as! String
// Now use Nuke (or another third party lib)
let request = ImageRequest(URL: NSURL(string:downloadURL)!)
Nuke.taskWith(request) { response in
// Do something with response
}
// Alternatively, you can use the Storage built-ins:
// Create a storage reference from the URL
let storageRef = storage.referenceFromURL(downloadURL)
// Download the data, assuming a max size of 1MB (you can change this as necessary)
storageRef.dataWithMaxSize(1 * 1024 * 1024) { (data, error) -> Void in
// Do something with data...
})
})
For more information, see Zero to App: Develop with Firebase, and it's associated source code, for a practical example of how to do this.
First, to be simple, how do I change a blank UIImage view to an image I have stored in Parse? This is my code so far
var query = PFQuery(className:"Content")
query.getObjectInBackgroundWithId("mlwVJLH7pa") {
(post: PFObject?, error: NSError?) -> Void in
if error == nil && post != nil {
//content.image = UIImage
} else {
println(error)
}
}
On top of just replacing the blank UIImageView, how may I make the image that it is replaced with random? I assume I can't use an objectId anymore, because that is specific to the row that it represents.
I would first retreive the objectIds from parse with getObjectsInBackgroundWithBlock, and then select a random objectId from that array in a variable called objectId. That way you save the user from querying every object from parse and using a lot of data doing it.
Second I would
getObjectInBackgroundWithId(objectId)
if error == nil {
if let image: PFFile = objectRow["image"] as? PFFile{
image.getDataInBackgroundWithBlock {
(imageData: NSObject?, error: NSError?) Void in ->
if let imageData = imageData {
let imageView = UIImageView(image: UIImage(data: imageData))
}
}
}
At least this works for me.
First off, you'll want to use query.findObjectsInBackgroundWithBlock instead of using query.getObjectInBackgroundWithId.
This will get you all of your PFObjects. Grab a random PFObject from the array it returns and now you have a single random PFObject that you can set the content.image to.
Let's say your PFObject has an 'image' attribute as a PFFile (what you should be using to store images with Parse.)
Simply convert the PFFile into a UIImage by doing something similar to the following:
if let anImage = yourRandomPFObject["image"] as? PFFile {
anImage.getDataInBackgroundWithBlock { (imageData: NSData?, error: NSError?) -> Void in
let image = UIImage(data:imageData)
content.image = image
}
}
I try to store array of UIImage, it used to work but somehow all of sudden it refused to work. I wrote this test example to check if it's stored properly and I guess the problem is somewhere here
let images = NSKeyedArchiver.archivedDataWithRootObject(self.globalImageArray)
NSUserDefaults.standardUserDefaults().setObject(images, forKey: "morningImages")
NSUserDefaults.standardUserDefaults().synchronize()
println("Images saved")
let images2 = NSUserDefaults.standardUserDefaults().objectForKey("morningImages") as? NSData
let imagesArray = NSKeyedUnarchiver.unarchiveObjectWithData(images2!) as! NSArray
var testArray = imagesArray as! [UIImage]
println("Check if images are loaded " + "\(testArray.count)")
The testArray.count is equal to zero which means it either fails to save them properly or fails to retrieve them.
I tried printing images2 and it does contain data, but printing the next value which is imagesArray leads to the result equals to "()" Guess the problem is with this line:
let imagesArray = NSKeyedUnarchiver.unarchiveObjectWithData(images2!) as! NSArray
Thanks for help in advance.
As others have pointed out, this really isn't the best use case for NSUserDefaults. This should be very simple to do by writing to and reading from files on disk.
Here's how I'd do it, and it's less code than saving to NSUserDefaults!
NSKeyedArchiver.archiveRootObject(self.globalImageArray, toFile: "/path/to/archive")
println("Images saved")
if let testArray = NSKeyedUnarchiver.unarchiveObjectWithFile("/path/to/archive") as? [UIImage] {
println("Check if images are loaded " + "\(testArray.count)")
} else {
println("Failed to load images.")
}
EDIT As it turns out this doesn't work on cached images (e.g. any UIImage loaded with either of the UIImage(named:) variants because the cached images don't seem to get serialized to disk. So, while the above works for not cached UIImages, the following works regardless of the image's cached status.
// Save the raw data of each image
if NSKeyedArchiver.archiveRootObject(imageArray.map { UIImagePNGRepresentation($0) }, toFile: archivePath) {
println("\(imageArray.count) Images saved")
} else {
println("failed to save images")
}
// Load the raw data
if let dataArray = NSKeyedUnarchiver.unarchiveObjectWithFile(archivePath) as? [NSData] {
// Transform the data items to UIImage items
let testArray = dataArray.map { UIImage(data: $0)! }
println("\(testArray.count) images loaded.")
} else {
println("Failed to load images.")
}