Firebase storage - PutData and download not being called when expected - ios

I'm making an app that involves an image view, the first time the user logs in they upload a photo to be their photo, like so:
let storage = FIRStorage.storage()
let storageRef = storage.referenceForURL("gs:xxxxxxxxxxxxxxxx")
let picRef = storageRef.child("images/" + "myname" + ".jpg")
let data = UIImagePNGRepresentation(pickedImage)
forceUpload(profPicRef, data: data!)
let uploadTask = picRef.putData(data, metadata: nil) { metadata, error in
if (error != nil) {
print(error)
} else {
print("uploaded")
// Metadata contains file metadata such as size, content-type, and download URL.
let downloadURL = metadata!.downloadURL
}
}
}
and then the next time they come back to this view controller, in ViewDidAppear I have this code that should download the picture and put it into the image View:
picRef.dataWithMaxSize(1 * 4096 * 4096) { (data, error) -> Void in
print("in download")
if (error != nil) {
print(error)
} else {
retPic = UIImage(data: data!)!
and then I set retPic to be the image view's image. The problem is, these methods are seemingly called at random. WHen I first go into this view controller, no picture appears in the image view and "in download" isn't printed. Also when I upload a picture, it isn't always uploaded and "uploaded" doesn't always print. After switching back and forth into this view controller again it eventually seems to load at random. I am wondering why these putData and dataWithMaxSize functions are called at seemingly random times, and how to force them to occur when I want them too. I had this issue with snapshots too, and moving them into ViewWillAppear seemed to work, but not here.
Thanks!

Related

Completion Handler to trigger collectionView Reload once Images are downloaded - Xcode [SWIFT]

I'm using a code I got off here to download some images and present them in a collection view.
Heres the code:
func downloadImage (url: URL, completion: () -> Void){
let session = URLSession(configuration: .default)
let downloadPicTask = session.dataTask(with: url) { (data, response, error) in
if let e = error {
print("Error downloading image \(e)")
} else {
if let res = response as? HTTPURLResponse {
print("Downloaded image with response code \(res.statusCode)")
if let imageData = data {
let image = UIImage(data: imageData)
self.globalImages.append(image!)
print("what does this say?-->", self.globalImages.count)
} else {
print("Couldn't get image: Image is nil")
}
} else {
print("Couldn't get response code for some reason")
}
}
}
completion()
downloadPicTask.resume()
}
And I'm calling the download image in view did load where URL is the URL. (this URL works and I can download image).
downloadImage(url: url) { () -> () in
collectionView.ReloadData()
}
The completion handler I've tried calls reloadData() way too soon. I'm wanting it to be called when the image is finished downloading? so then the image can be displayed as soon as it's downloaded.
What is wrong with this code?
Please help!
You would call the completion handler as soon as you have the image. So, in your code:
if let imageData = data {
let image = UIImage(data: imageData)
self.globalImages.append(image!)
print("what does this say?-->", self.globalImages.count)
// call the completion handler here
Be aware, though, that you are likely to have other issues caused by data sharing across multiple threads, and also that your idea of storing the downloaded images successively in an array (globalImages) is not likely to work correctly.

How to finish the upload of images to Firebase Storage and then save the imageUrl to the Firebase Database

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.

how can I use Api for downloading pictures in all of the view controllers in page view controller?

I have a page view controller in my app and this page view controller has 4 view controller - so I have Image View in all of these page View controller and I want to use Json Api to download images in these pages
Here is my Code But I copied this code in all of my view controllers class so I just write one of them here
func refreshingphoto1() {
let firstImageURL = URL(string: "http://img.autobytel.com/car-reviews/autobytel/11694-good-looking-sports-cars/2016-Ford-Mustang-GT-burnout-red-tire-smoke.jpg")!
let session = URLSession(configuration: .default)
// Define a download task. The download task will download the contents of the URL as a Data object and then you can do what you wish with that data.
let downloadPicTask = session.dataTask(with: firstImageURL) { (data, response, error) in
// The download has finished.
if error != nil {
print("Error downloading picture")
self.firstImageJson.image = UIImage(named: "1.jpg")
} else {
// No errors found.
// It would be weird if we didn't have a response, so check for that too.
if (response as? HTTPURLResponse) != nil {
print("Downloaded picture with response code")
if let imageData = data {
// Finally convert that Data into an image and do what you wish with it.
let image = UIImage(data: imageData)
self.firstImageJson.image = image
// Do something with your image.
} else {
print("Couldn't get image: Image is nil")
self.firstImageJson.image = UIImage(named: "1.jpg")
}
} else {
print("Couldn't get response code for some reason")
self.firstImageJson.image = UIImage(named: "1.jpg")
}
}
}
downloadPicTask.resume()
}
}
**This Will work But Not Completely true **
and here is the picture of my story Board
as you see in the picture I have Page VC2 that is page view controller and four view controllers that connected to the page VC2
The fact that you have multiple VC has little to do with the task of downloading images.
However, apart for a lack of abstraction, you are on the right track : using URLSession and URLSessionTask is the right way to do by using Apple APIs.
You may be interested in the following, though ; to abstract things a lot of developer use this third-party library named Alamofire and specifically for your case AlamofireImage.
For example with AlamofireImage you'd just have to do
let imageView = UIImageView(frame: frame)
let url = URL(string: "https://httpbin.org/image/png")!
let placeholderImage = UIImage(named: "placeholder")!
imageView.af_setImage(withURL: url, placeholderImage: placeholderImage)

Downloaded image doesn't always show up immediately

For some reason my photoimg will not update consistantly , sometimes it does sometimes it doesn't.
I'm pretty sure it has something to do with async calls but I've been stuck trying to figure out the root reason why its not updating. So this is in my mainVC and for a user to upload/update image they go to the settingsVC and when they segue back sometimes it shows to the updated image, other times still shows the old image , other times showing nothing . But oddly if I click on my settings and dismiss it then the image will show updated.
So I think my issue lies where I'm calling my method and my async queue.
func fetchProfileImage() {
Dataservice.dataService.USERS_REF_CURRENT_PROFILE_IMAGE.downloadURL { (url, error) in
if error != nil {
}
else {
let url = url?.downloadURL
URLSession.shared.dataTask(with: url!, completionHandler: { (data, resonse, error) in
if error != nil {
print("Fetching did not download \(error.debugDescription)")
}
if let data = data {
print("Fetching Image did download data")
DispatchQueue.main.async {
self.profilePhoto.image = UIImage(data: data)
}
}
}).resume()
}
}
}
Why not just use the built in download mechanism, which always presents callbacks on the main thread*:
let image: UIImage!
let ref = FIRStorage.storage().reference(forURL: Dataservice.dataService.USERS_REF_CURRENT_PROFILE_IMAGE)
ref.data(withMaxSize: 1 * 1024 * 1024) { data, error in
if let error = error {
// Uh-oh, an error occurred!
} else {
// Data for your profile image is returned
image = UIImage(data: data!)
}
}
*unless you explicitly change the thread by providing your own queue ;)
after you perform async, you need to back to main thread to set the download image
DispatchQueue.main.async {
DispatchQueue.main.sync {
self.profilePhoto.image = UIImage(data: data)
}
}

Images loading in incorrectly even with cache

if let toID = message.chatPartnerId() {
firebaseReference.child(toID).observeSingleEvent(of: .value, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: Any] {
cell.nameLabel.text = dictionary["displayname"] as? String
let pic = dictionary["pictureURL"] as! String
print("THIS IS THE URL FOR EACH DISPLAYNAME")
print(dictionary["displayname"] as? String)
print(pic)
if let imageFromCache = MainPageVC.imageCache.object(forKey: pic as NSString) {
cell.pictureLabel.image = imageFromCache
} else {
let requested = URLRequest(url: URL(string: pic )!)
URLSession.shared.dataTask(with: requested) {data, response, err in
if err != nil {
print(err)
} else {
DispatchQueue.main.async {
let imageToCache = UIImage(data: data!)
MainPageVC.imageCache.setObject(imageToCache!, forKey: pic as NSString)
//cell.pictureLabel.image = nil
cell.pictureLabel.image = imageToCache
}
}
}.resume()
}
}
})
}
return cell
}
I'm running this code in my cellForRowAtIndexPath and I'm getting a ton of really bad behavior. I'm also getting similar behavior on other pages but for some reason this block of code with about a 90% consistency returns incorrect information for cells.
I get a lot of duplicate pictures being used, displaynames in the wrong places, but when I'm actually clicking into a person, my detail page shows the correct information every single time. That code is the typical didSelectRowAtIndexPath and passing the person.
What I don't understand is why on the initial load of this page all of the information is screwed up, but if I click into someone and come back the entire tableview has correct names and pictures. The names/pics also fix if I scroll a cell off the screen then come back to it.
I'm getting this behavior all over my app, meanwhile I see caching/loading done like this everywhere. Is it because I'm running the code in my cellForRowAtIndexPath? The only difference I see is that I'm running it there instead of creating a function inside of my Person class that configures cells and running it like that. What I don't understand is why that would make a difference because as far as I'm aware running a function within cellforRowAtIndexpath would be the same as copy-pasting that same code into there?
Any ideas/suggestions?
Edit: I'm getting a very similar situation when I'm running the following code:
self.PersonalSearchesList = self.PersonalSearchesList.sorted{ $0.users > $1.users }
self.tableView.reloadData()
Where I'm sorting my array before reloading my data. The information sometimes loads in incorrectly at first, but once I scroll the cell off the screen then come back to it it always corrects itself.
if you are using swift 3 here are some handy functions that allow you to save an image to your apps directory from an URL and then access it from anywhere in the app:
func saveCurrentUserImage(toDirectory urlString:String?) {
if urlString != nil {
let imgURL: URL = URL(string: urlString!)!
let request: URLRequest = URLRequest(url: imgURL)
let session = URLSession.shared
let task = session.dataTask(with: request, completionHandler: {
(data, response, error) -> Void in
if (error == nil && data != nil) {
func display_image() {
let userImage = UIImage(data: data!)
if let userImageData = UIImagePNGRepresentation(userImage!) {
let filename = self.getDocumentsDirectory().appendingPathComponent("userImage")
try? userImageData.write(to: URL(fileURLWithPath: filename), options: [.atomic])
}
}
DispatchQueue.main.async(execute: display_image)
}
})
task.resume()
}
}
and then access it with any view controller using this:
extension UIViewController {
func getImage(withName name: String) -> UIImage {
let readPath = getDocumentsDirectory().appendingPathComponent(name)
let image = UIImage(contentsOfFile: readPath)
return image!
}
}
and finally calling it like this:
cell.pictureLabel.image = getImage(withName: "userImage")
If you can run the saveCurrentUserImage function prior to running cellForRowAtIndexPath then you can just check if the photo is nil in the directory before attempting to download it. You might be getting funny behavior when the page initially loads because you have multiple network calls going on at once. I wouldn't recommend making any network calls in cellForRowAtIndexPath because every time the cells are re-initialized it's going to make that network call for each cell.
Hope it helps!
EDIT: This method of image saving and retrieval is for images that you want to persist. If you want to erase them from memory you'll have to delete them from your directory.

Resources