How can I make multiple uploads to Firebase Storage asynchronous? - swift

I've got an array of data which I want uploading to Firebase Storage in order, so the download urls which are retrieved are in order too.
I've tried to use DispatchGroup and DispatchSemaphore for this, but I'm guessing I've inserted them in incorrect positions in my code, because my print statement which prints the index and the urlString aren't printed in order. How can I fix this?
import Foundation
import FirebaseStorage
func uploadToStorage(dataArray: [Data],
completion: #escaping (_ success: Bool, _ urlsAsStringArray: [String]) -> Void) {
let dispatchGroup = DispatchGroup()
let dispatchSemaphore = DispatchSemaphore(value: 0)
let dispatchQueue = DispatchQueue(label: "someTask")
var urlsAsStringArray: [String] = []
dispatchQueue.async {
for (index, data) in dataArray.enumerated() {
dispatchGroup.enter()
dispatchSemaphore.signal()
let imageId = NSUUID().uuidString
let fileName = "\(imageId).jpg"
let folder = Storage.storage().reference()
.child("photos")
folder.child(fileName).putData(data, metadata: nil) { (metadata, error) in
if let error = error {
print("\n \nError with Firebase Storage photo upload:")
print(error)
dispatchGroup.leave()
dispatchSemaphore.wait()
completion(false, [])
} else {
print("\n \nSuccess with Firebase Storage photo upload")
folder.child(fileName).downloadURL { (url, error) in
if let error = error {
print("\n \nError with retrieving Firebase Storage photo download URL:")
print(error)
dispatchGroup.leave()
dispatchSemaphore.wait()
completion(false, [])
} else {
if let urlAsString = url?.absoluteString {
print("\n \nSuccess with retrieving Firebase Storage photo download URL")
print("\n \nPhoto message to be sent to Firebase Firestore has URL link: \(urlAsString)")
urlsAsStringArray.append(urlAsString)
print("Index is: \(index). Photo uploaded has url: \(urlAsString)")
dispatchGroup.leave()
dispatchSemaphore.wait()
if index == dataArray.count - 1 {
completion(true, urlsAsStringArray)
}
}
}
}
}
}
}
}
}

Related

How to upload video to Firebase Storage in background - swift - Programmatically

I'm sending a video and the thumbnail Image from the client to Firebase Storage and I save the respective urls in Firestore using this procedure:
fileprivate func sendVideoToDatabase(urlVideo : URL){
ProgressHUD.showProgress(0.0, interaction: true)
guard let uid = Auth.auth().currentUser?.uid else { return }
let videoName = UUID().uuidString
let storageRef = Storage.storage().reference().child("Videos").child("\(videoName).mp4")
let storageThumbnailsRef = Storage.storage().reference().child("thumbnails")
storageRef.putFile(from: urlVideo, metadata: nil) { (metadata, error) in
if let error = error{
print("failed upload video \(error.localizedDescription)")
ProgressHUD.showFailed()
return
}
ProgressHUD.showProgress(0.1, interaction: true)
storageRef.downloadURL { (urlVideo, error) in
if let error = error{
print(error.localizedDescription)
ProgressHUD.showFailed()
return
}
ProgressHUD.showProgress(0.2, interaction: true)
let firestoreMoments = Firestore.firestore().collection("Moments").document(uid).collection("Moments")
ProgressHUD.showProgress(0.3, interaction: true)
self.getImageFromVideo(url: urlVideo!, at: 1) { (imageRetrieved) in
let imageID = "\(UUID().uuidString).jpg"
guard let imageData = imageRetrieved?.jpegData(compressionQuality: 0.35) else { return }
ProgressHUD.showProgress(0.4, interaction: true)
storageThumbnailsRef.child(imageID).putData(imageData, metadata: nil) { (metadata, error) in
ProgressHUD.showProgress(0.5, interaction: true)
if let err = error{
print("impossible to add a thumbnail image: \(err.localizedDescription)")
ProgressHUD.showFailed()
return
}
ProgressHUD.showProgress(0.6, interaction: true)
storageThumbnailsRef.child(imageID).downloadURL { (url, err) in
if let err = err{
print("impossible to download image: \(err.localizedDescription)")
ProgressHUD.showFailed()
return
}
ProgressHUD.showProgress(0.7, interaction: true)
guard let thumbnailUrl = url?.absoluteString else { return }
let docRef = firestoreMoments.document()
ProgressHUD.showProgress(0.8, interaction: true)
docRef.setData(["type" : "video",
"uid": uid,
"videoUrl" : urlVideo!.absoluteString,
]) { (error) in
if let error = error{
print(error.localizedDescription)
ProgressHUD.showFailed()
return
}
ProgressHUD.showProgress(1, interaction: true)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
ProgressHUD.dismiss()
})
}
}
}
}
}
}
}
Meanwhile I show a progressHUD that prevents any user interaction from the client side until the uploading process is complete. The problem is that it takes a lot of time for a video of decent size (20MB).
I was wondering is there a way to implement an uploading process that goes on in the server side or background? so that the client can go on and perform other tasks meanwhile?

Swift: upload multiple images to Firestore and assign URLs to object

I'm making an app whereby users post 2 images. I'm using Firebase for storage and as my database.
In my method to upload the images what I had wanted to do was to essentially use this method to return the URLs separately as well. I had written the following:
private func uploadImage(image: UIImage) -> URL? {
let randomName = UUID()
let storageRef = storage.reference().child("\(randomName)/png")
guard let uploadData = image.pngData() else { return nil}
var imageUrl: URL?
storageRef.putData(uploadData, metadata: nil) { (metadata, error) in
if error != nil {
print(error?.localizedDescription)
return
}
storageRef.downloadURL { (url, error) in
if error != nil {
print(error?.localizedDescription)
} else {
imageUrl = url
}
}
}
return imageUrl
}
And then I wrote the following 'post' method which is run when the submit button is tapped:
#objc func post() {
if let question = questionText.text,
let hashtagText = hashtagTextField.text,
let userHandle = Auth.auth().currentUser?.email,
let firstImage = left.image,
let secondImage = right.image,
let firstImageURL = uploadImage(image: firstImage)?.absoluteString,
let secondImageURL = uploadImage(image: secondImage)?.absoluteString
{
db.collection("posts").addDocument(data: [
"firstImage" : firstImageURL,
"secondImage" : secondImageURL,
"question" : question,
"hashtagText" : hashtagText,
"userHandle" : userHandle
]) { (error) in
if let e = error {
print("There was an issue saving data to Firestore, \(e)")
} else {
print("Successfully saved data")
self.dismiss(animated: true, completion: nil)
}
}
}
}
However, obviously the first method is not going to work as the closure is run after imageUrl is returned, therefore returning nil.
I've been trying to figure out how to manage this scenario - I had considered using a loop to populate an array of images but this got messy and I'm sure it is not the standard way to handle this. Any help would be greatly appreciated.
The return imageUrl is in the wrong place. It will return before Firebase has had time to store the image and return the url.
Additionally, the name of the file is not going to work. You currently have
storage.reference().child("\(randomName)/png") // xxxxx/png?
when it should be
storage.reference().child("\(randomName).png") // xxxxx.png
You can't 'return' data from a Firebase closure because firebase is asynchronous - a completion handler may possibly be a solution, but we don't know what the total use case is.
Let's assume you want want to store a users vacation picture in storage and then store that url in Firestore
private func uploadImage(image: UIImage) {
guard let uid = Auth.auth().currentUser?.uid else { return } //this users uid
let storageRef = storage.reference().child(uid).child("vacation.png")
//the path will be storage/users uid/vacation.png
guard let uploadData = image.pngData() else { return nil}
storageRef.putData(uploadData, metadata: nil) { (metadata, error) in
if error != nil {
print(error?.localizedDescription)
return
}
storageRef.downloadURL { (url, error) in
if error != nil {
print(error?.localizedDescription)
} else {
if url != nil {
//it's here where we store the imageUrl in Firestore
let dict = ["theUrl": url?.absoluteURL)]
let userRef = self.db.collection("users").document(uid)
//self.db points to *my* Firestore
userRef.collection("my_pics").addDocument(data: dict)
//will store in firstore/users/uid/docId/theUrl: the url
}
}
}
}
}

How to download URL from firebase? not receiving an error but not downloading either?

When using an image picker to grab the image selected and place in firebase storage I want to be able to download the URL and take place of the profile image inside the app. Unfortunately, when the process reaches the URLSession in the script nothing happens. There is not an error display nor does it dispatchQueue. The app will not crash but just skip over everything. Any ideas or suggestions to a code fix?
if let profileImageUploadedData = self.profileImage.image, let uploadData = profileImage.image?.jpegData(compressionQuality: 0.1)
{
storageRef.putData(uploadData, metadata: nil, completion: { (metadata, error) in
if error != nil
{
print("Downdloading putData Error: \(error!.localizedDescription)")
return
}
storageRef.downloadURL(completion: { (url, error) in
if error != nil
{
print("DownloadURL ERROR \(error!.localizedDescription)")
return
}
if let profileImageUrl = url?.absoluteString
{
print("Profile image uploading...")
let values = ["profileImageUrl": profileImageUrl]
let url = URL(fileURLWithPath: profileImageUrl)
URLSession.shared.dataTask(with: url) { (data, response, error) in // ERROR OCCURING NEED TO FIX
if error != nil
{
print("* URL SESSIONS ERROR: \(error!)")
return
}
DispatchQueue.main.async
{
print("Trying to register profile")
self.registerUserIntoDatabaseWithUID(uid: uid, values: values as [String : AnyObject])
self.profileImage.image = UIImage(data: data!)
print("Dispatch: \(data!)")
}
print("Profile image successfull uploaded to storage")
}
}
})
}).resume()
print("** Profile Image Data Uploaded:\(profileImageUploadedData)")
}
}
func registerUserIntoDatabaseWithUID(uid: String, values: [String: AnyObject])
{
print("Registering to database")
let dataReference = Database.database().reference(fromURL: "URL String")
let usersReference = dataReference.child("users").child(uid)
usersReference.updateChildValues(values, withCompletionBlock: { (err, reference) in
if err != nil
{
print(err!)
return
}
// self.profileImage.image = values["profileImageUrl"] as? UIImage
// self.fetchProfileImage()
self.dismiss(animated: true, completion: nil)
print("Saved user sussccessfully in database")
})
}
}
This is a big question that is actually asking for a number of different answers so let's just focus on one;
How to authenticate a user, get a url stored in Firebase Database that
references an image stored in Firebase Storage, then download that
image.
Here we go
First - authenticate a user
Auth.auth().signIn(withEmail: user, password: pw, completion: { (auth, error) in
if let x = error {
//handle an auth error
} else {
if let user = auth?.user {
let uid = user.uid
self.loadUrlFromFirebaseDatabase(withUid: uid)
}
}
})
now the user is authenticated, get the image location url from Firebase Database
func loadUrlFromFirebaseDatabase(withUid: String) {
let thisUserRef = self.ref.child("users").child(withUid)
let urlRef = thisUserRef.child("url")
urlRef.observeSingleEvent(of: .value, with: { snapshot in
if let url = snapshot.value as? String {
self.loadImageUsingUrl(url: url)
} else {
print("no image for user")
}
})
}
Now that we have the location of the image in Firebase Storage, get it
func loadImageUsingUrl(url: String) {
let storage = Storage.storage()
let imageRef = storage.reference(forURL: url)
imageRef.getData(maxSize: 1 * 1024 * 1024) { data, error in
if let error = error {
print("error downloading \(error)")
} else {
if let image = UIImage(data: data!) {
//do something with the image
} else {
print("no image")
}
}
}
}

How to get imageUrl from firebase storage and store in firebase database in Xcode 10 swift 4.2

Here is the code that I'm using
#objc func handleSignUp() {
guard let email = emailTextField.text, email.characters.count > 0 else { return }
guard let username = usernameTextField.text, username.characters.count > 0 else { return }
guard let password = passwordTextField.text, password.characters.count > 0 else { return }
Auth.auth().createUser(withEmail: email, password: password, completion: { (user, error) in
if let err = error {
print("Failed to create user:", err)
return
}
print("Successfully created user:", user?.user.uid)
// guard let image = self.plusPhotoButton.imageView?.image else { return }
// guard let uploadData = UIImageJPEGRepresentation(image, 0.3) else { return }
guard let uploadData = self.plusPhotoButton.imageView?.image?.jpegData(compressionQuality: 0.3) else{return}
let filename = NSUUID().uuidString
Storage.storage().reference().child("profile_images").child(filename).putData(uploadData, metadata: nil, completion: { (metadata, err) in
if let err = err {
print("Failed to upload profile image:", err)
return
}
//this code isn't correct for swift4.2
//guard let profileImageUrl = metadata?.downloadURL()?.absoluteString else { return }
func getDownloadURL(from path: String, completion: #escaping (URL?, Error?) -> Void) {
let storageReference = Storage.storage().reference(forURL: "gs://instagramfirebase-60be5.appspot.com")
let storageRef = storageReference.child(path).downloadURL(completion: completion)
}
print("Successfully uploaded profile image")//,profileImageUrl
guard let uid = user?.user.uid else { return }
let dictionaryValues = ["username": username] as [String : Any]//, "profileImageUrl": profileImageUrl
let values = [uid: dictionaryValues]
Database.database().reference().child("users").updateChildValues(values, withCompletionBlock: { (err, ref) in
if let err = err {
print("Failed to save user info into db:", err)
return
}
print("Successfully saved user info to db")
})
})
})
}
Snapshots are as followed :
I am confused about how to get the imageUrl from firebase storage and store it in firebase database in Xcode 10 swift 4.2
Please help, Thank you for your time.
You can get the download url easily by using the following code inside the closure of your image-upload:
storageRef.downloadURL(completion: { (url, error) in
if let err = error{
// error happened - implement code to handle it
print(err)
} else {
// no error happened; so just continue with your code
print(url?.absoluteString) // this is the actual download url - the absolute string
}
Please note: Don't store the downloadURL on its own as Firebase can change tokens of the downloadURL, I'd suggest to always grab a hold of the full storage path.
I hope I was able to help.

How to download multiple images from firebase?

I am trying to download multiple images to display in a collection view cell. But downloading just one exceeds the download size. If I upgrade the download size to a higher value the app crashes after 3 or more images are downloaded. How can I download the images and show them on my collection view effectively?
This is my code to upload:
func uploadImage(_ image: UIImage, uid: String,categoryIndex:Int, spotIndex:Int,completion: #escaping ((_ url: URL?) ->())) {
let storageReference = Storage.storage().reference().child("user/\(uid)/\(categoryIndex)/\(spotIndex).jpg")
guard let imageData = UIImage(data: image.jpegData(compressionQuality: 0.8)!) else { return }
let metaData = StorageMetadata()
metaData.contentType = "img/jpg"
storageReference.putData(imageData.jpegData(compressionQuality: 0.8)!, metadata: metaData, completion: { metaData, error in
if error == nil, metaData != nil {
// success
storageReference.downloadURL(completion: { (url, error) in
guard let downloadURL = url else {
print("ERROR in image link")
return
}
completion(downloadURL)
})
} else {
// Fail
completion(nil)
}
})
}
This is my code to download:
// Download image using the category index and spot index to get the correct image
func downloadImages(folderPath: String, categoryIndex: Int, spotIndex: Int,success: #escaping (_ image: UIImage)->(), failure:#escaping (_ error:Error)->()) {
let reference = Storage.storage().reference(withPath: "\(folderPath)/\(categoryIndex)/\(spotIndex).jpg")
reference.getData(maxSize: (1 * 1024 * 1024)) { (data, error) in
if let error = error {
print(error.localizedDescription)
failure(error)
} else {
if let data = data {
let myImage:UIImage! = UIImage(data: data)
success(myImage)
}
}
}
}
I found the solution. The compressionQuality should be lower than 0.8
I used 0.25 and seems to be working perfectly.