Using Firebase Storage for Uploading Images - swift

I can only upload a single photo to FirebaseStorage.
All the tutorials I have seen on youtube use FirebaseStorage to save user's profile pictures. However, I am trying to make a restaurant app where I should be able to keep several pictures and not only one. My code right now only allows me to keep one. As soon as I try to upload a second image, it erases the first one.
Here's my code:
#IBAction func agregarComidaFav(_ sender: Any) {
guard let imageData = imagen?.jpegData(compressionQuality: 0.4) else {
return
}
let storageRef = Storage.storage().reference(forURL: "gs://comidas-68043.appspot.com")
let storageProfileRef = storageRef.child("ComidasFavoritas")
let metadata = StorageMetadata()
metadata.contentType = "image/png"
storageRef.child("comidasFavoritas/png").putData(imageData, metadata: metadata) { (storageMetaData, error) in
if error != nil {
print(error?.localizedDescription ?? "")
return
}
}
storageProfileRef.downloadURL(completion: { (url, error) in
if let metaImageUrl = url?.absoluteString {
print(metaImageUrl)
self.imagenURL = metaImageUrl
}
})
var ref: DocumentReference? = nil
ref = db.collection("comidasFavoritas").addDocument(data: [
"imagenURL" : imagenURL
]) { err in
if let err = err {
print("Error agregando comida: \(err)")
} else {
let id = ref!.documentID
print("Comida agregado con ID: \(id)")
}
}
}

This line:
storageRef.child("comidasFavoritas/png").putData(...)
...guarantees that each time, the data is going to be written to "comidasFavoritas/png"
I don't know anything about how you're keeping track of your data/images in your app, but you'll probably need some way to keep track of an ID for that image. Then, when you store it, you could store it like:
storageRef.child("comidasFavoritas/png\(imageId)").putData(...)
You'd probably also want to store that ID in your database when you store the image URL.

Related

How To Fetch Multiple Uploaded Images By Current User - Firebase and Swift

I wanted to know what is the best way to fetch images which are uploaded by the user on my app. Currently, the images are all being saved in storage. I assume this is not the correct way as it has nothing to do with userId? Perhaps, I need to save user media in real time database in order to fetch their images? If someone could help me, that will be much appreciated.
Thank you.
guard let image = imageView.image, let data = image.jpegData(compressionQuality: 1.0)
else {
print("something went wrong")
return
}
let imageName = UUID().uuidString
let imageReference = Storage.storage().reference().child(MyKeys.imagesFolder).child(imageName)
imageReference.putData(data, metadata: nil) { (metadata, err) in
if let err = err {
print ("Error")
return
}
imageReference.downloadURL(completion: { (url, err) in
if let err = err {
print ("Error")
return
}
guard let url = url else {
print ("Error")
return
}
let dataReference = Firestore.firestore().collection(MyKeys.imagesCollection).document()
let documentUid = dataReference.documentID
let urlString = url.absoluteString
let data = [
MyKeys.uid:documentUid,
MyKeys.imageUrl: urlString,
MyKeys.imageTitle: self.imageTitle.text!,
] as [String : Any]
dataReference.setData(data) { (err) in
if let err = err {
print ("Error")
return
}
}
})
}
}
A pretty common pattern is to store the user ID in the image's path in Firebase Storage. For example,
let imageName = //...
guard let uidString = Auth.auth().currentUser?.uid else {
return
}
let imageRefPath = "\(uidString)/\(imageName)"

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
}
}
}
}
}

Swift storing PKPass with Firebase

I'm trying to save the PKPass with Firebase so that I'm not generating the pass every time the user wants to add the pass to the waller or send the pass. I'm storing the pass.passURL in my firebase database and then fetching it again when the user wants to do something with the pass. Right now all that is happening is the Wallet is opening but the pass isn't showing up. Is this the best way to store the PKPass
Save Function:
func saveTicketIdToDB(pass: PKPass) {
guard let uid = Auth.auth().currentUser?.uid else { return }
guard let postId = post?.postId else { return }
guard let paymentId = paymentId else { return }
guard let passUrl = pass.passURL as? URL else { return }
let passUrlString: String = passUrl.absoluteString
let values = ["passUrl": passUrlString]
let db = Firestore.firestore()
db.collection("payments").document(postId).collection("purchases").document(paymentId).updateData(values) { (error) in
if let error = error {
print("There was an error", error.localizedDescription)
return
}
print("Successfully saved pkpass into database.")
}
}
Opening in wallet:
guard let passUrlData = URL(string: passUrl) else { return }
if #available(iOS 10.0, *) {
UIApplication.shared.open(passUrlData, options: [:], completionHandler: nil)
} else {
UIApplication.shared.openURL(passUrlData)
}
You need to store the entire PKPass bundle as a data blob. Then re-create it using PKPass.init(data: ...) and use the PassKit APIs to see if it's in the Wallet already.

Check Firebase Storage before creating a new profile Image URL

I am having a bit of an issue checking Firebase Storage when a user logs into my app. When a user logs in an image is created and stored successfully in Firebase database and storage, but every time a user logs out and logs back into the app the profileImage file (not the child value url string) is duplicated in Firebase Storage as a new and separate image file.
I would like the app when logging in to check the Firebase storage for the profileImage file, and if it exists then do not re-create that same image in storage. I understand the logic of checking Firebase for image and if does not exist, than create new image. I am just having some trouble with the syntax. Thanks for help in advance!
// login user with Facebook
func loginWithFacebook() {
let accessToken = FBSDKAccessToken.current()
guard let accessTokenString = accessToken?.tokenString else { return }
let credentials = FIRFacebookAuthProvider.credential(withAccessToken: accessTokenString)
FIRAuth.auth()?.signIn(with: credentials, completion: { (user, error) in
if error != nil {
print("Something went wrong with our FB user: ", error ?? "")
return
}
guard let uid = user?.uid else {
return
}
let imageName = NSUUID().uuidString
let storageRef = FIRStorage.storage().reference().child("profile_images").child("\(imageName).png")
let photoUrl = user?.photoURL
// check to see if current user already has stored image with URL
if FIRStorage.storage().reference().child("profile_images").child("\(imageName).png") == nil {
if let imageData = NSData(contentsOf: photoUrl!) {
storageRef.put(imageData as Data, metadata:nil) {
(metadata, error) in
if error != nil {
print(error!)
return
} else {
if let profileImageUrl = metadata?.downloadURL()?.absoluteString {
let values = ["name": user!.displayName!, "email": user!.email!, "profileImageUrl": profileImageUrl]
self.registerUserWithUID(uid: uid, values: values as [String : AnyObject])
}
}
}
}
} else {
print("image already exists")
}
print("Successfully logged in with our user: ", user ?? "")
self.delegate?.finishLoggingIn()
})
private func registerUserWithUID(uid: String, values: [String: AnyObject]) {
// create items in database upon creating user ---------------
let ref = FIRDatabase.database().reference()
let usersReference = ref.child("users").child(uid)
usersReference.updateChildValues(values, withCompletionBlock: { (err, ref) in
if err != nil {
print(err!)
return
}
print("user has been saved to Firebase database")
})
}
I think the issue is with the "if" condition. You are referencing the location, but not checking with the actual data in that location.
You can implement that using .observerSingleEventOf().
private func checkUser (imageName:String, _ completionHandler: #escaping(Bool) -> Void)
{
let userExists = FIRStorage.storage().reference().child("profile_images")
userExists.observeSingleEvent(of: .value, with:{(snapshot) in
if snapshot.hasChild(imageName){
print ("Image already exists")
completionHandler(false)
}
else
{
print ("Image does not exist")
print ("store the image")
}
})
}
`

Firebase upload image

I'm trying to upload an image to Firebase storage using following code:
let storageRef = FIRStorage.storage().reference()
let imageRef = storageRef.child("AAA").child("BBB").child("CCC.jpeg")
let metaData = FIRStorageMetadata()
metaData.contentType = "image/jpeg"
if let uploadData = UIImageJPEGRepresentation(sampleImage, 0.5){
imageRef.put(uploadData, metadata: metaData, completion: { (metaData, error) in
print("finished")
if error != nil{
print(error!.localizedDescription)
}else{
print("success")
}
})
}else{
print("Cannot convert image to JPEG format")
}
The problem is that The above code doesn't do anything, in other words, nothing prints out. I have tried uploading strings to Firebase database, and it succeed. I've also checked the rules of Firebase storage:
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read: if true;
allow write: if request.auth != null;
}
}
}
So, what did i do wrong? Is there anything that I need to set up? Please help!!
Disclaimer: Let's just say my middle name isn't SwiftLint.
Try this, and make sure your database rules are similar as well. You should get a link in your database that will point you to the image in storage.
if let theData = UIImageJPEGRepresentation(sampleImage, 0.5) {
self.helper(photoData:theData)
}
// ^^ this can happen elsewhere ^^
func upload(photoData:Data) {
let imgName = Int(round(Date().timeIntervalSince1970)
let metadata = FIRStorageMetadata()
metadata.contentType = "image/jpeg"
let imageRef = FIRStorage.storage().reference().child("AAA/BBB/\(imgName).jpg")
imageRef.put(photoData, metadata: metadata) { (metadata, error) in
if let error = error {
print("Error uploading: \(error)")
return
}
let data:[String:Any] = [
"timeStamp":"\(Date())",
"image":metadata!.downloadURL()!.absoluteString
]
let dbRef = FIRDatabase.database().reference()
dbRef.child("imageUploads").childByAutoId().setValue(data)
}
}
Well, both of my original solution and #MGTLA solution are correct, the problem turns out to be the while loop after uploading image...which is unrelated to this topic.