Changing values in storage metadata in Firebase - ios

In my iOS app, users can upload profile pictures which are upload to the storage in Firebase. I have saved the URL of the profile pictures on to the database so I can know which URLs correspond to which users using the following code:
let storageRef = FIRStorage.storage().reference()
let fileRef = storageRef.child("pages/").child(UUID().uuidString + ".jpg")
_ = fileRef.put(UIImageJPEGRepresentation(image, 0.75)!, metadata: nil) { (metadata, error) in
if let error = error {
SCLAlertView().showError("Error", subTitle: error.localizedDescription)
return
} else {
let downloadURL = metadata!.downloadURL()
let dictionary: [String: Any] = ["UserID": uid, "PageName": self.pageTitle.text!, "PFPURL": downloadURL!, "Tags": self.tags, "Likes": [uid]]
let reference = FIRDatabase.database().reference().child("Pages").childByAutoId()
reference.setValue(dictionary, withCompletionBlock: { (error, ref) in
if let error = error {
SCLAlertView().showError("Error", subTitle: error.localizedDescription)
return
}
self.performSegue(withIdentifier: "setupComplete", sender: self)
})
}
}
However, I would like these pictures to be editable. Is it possible I can replace the image in the storageRef yet still keep the same URL? If so, how would I accomplish that? Thanks!

You can't keep the same download URL. When the photo is edited, you'll need to update the value of "PFPURL" in the database to be the new URL.

Related

Display Image downloaded from downloadURL generated by Firebase

I need to display the images that have been stored on the storage on Firebase. Right now, I only tracked the images using the link generated by function downloadURL:
func UploadImage(imageData: Data, path: String, completion: #escaping (String) -> ()){
let storage = Storage.storage().reference()
let uid = Auth.auth().currentUser?.uid
storage.child(path).child(uid ?? "").putData(imageData, metadata: nil) { (_, err) in
if err != nil{
completion("")
return
}
// Downloading Url And Sending Back...
storage.child(path).child(uid ?? "").downloadURL { (url, err) in
if err != nil{
completion("")
return
}
completion("\(url!)")
}
}
}
So all I can get is a hyperlink that is like: https://firebasestorage.googleapis.com/v0/b/getting-started-20f2f.appspot.com/o/profile_Photos%2FGQ1KR9H1mLZl2NAw9KQcRe7d72N2?alt=media&token=473ce86c-52ba-42ec-be71-32cc7dc895d7.
I refer to the official documentation, it seems that only when I have the name of the image file can I download it to an ImageView or UIImageView object. However, the link does not make any sense to me, so what can I do?
EDIT
I actually tried a solution provided by the official documentation:
func imageDownloader(_ imageURL: String) {
let store = Database.database().reference()
let uid = Auth.auth().currentUser?.uid
let imageRef = store.child(imageURL)
var myImageView = UIImageView()
imageRef.getData(completion: { (error, data) in
if let error = error {
// Uh-oh, an error occurred!
} else {
// Data for "images/island.jpg" is returned
let image = UIImage(data: data!)
}
})
}
But it suggests that I need to change something because Cannot convert value of type 'DataSnapshot' to expected argument type 'Data'.
If you're storing the image paths in Firestore, actually the exact file name does not matter if there is only one file available under the fork. So you just need to specify the path.
To then download the image from Storage, construct the path and download:
let uid = Auth.auth().currentUser?.uid
Storage.storage().reference().child("the\path\to\your\uid\collection").child(uid).getData(maxSize: 1048576, completion: { (data, error) in
if let data = data,
let img = UIImage(data: data) {
// do something with your image
} else {
if let error = error {
print(error)
}
// handle errors
}
})
You are uploading to Storage.storage(), but then in your imageDownloader, you're attempting to use Database.database(), which has a similar-looking API, but is, in fact, different.
Make sure to use Storage.storage() and that the closure parameters are in the order data, error in.
Finally, right now in your imageDownloader, it doesn't look like you're doing anything yet with var myImageView = UIImageView(), but keep in mind that you won't have access to the UIImage until the async getData completes.
Store your images at Firebase Storage & then retrieve using this code.
Storage.storage().reference.child("ProfilePhotos").child("ImageName").downloadURL {(url, _) in
DispatchQueue.main.async {
guard let url = url else { return }
imageView.setImage(with: url, placeholder: UIImage(named: "dummyImage"))
}
}

Create a new image every time user takes a new photo in firebase storage

In my app user presses the button to take the photo and then it uploads to firebase storage but every time I do a new photo the old one disappears and it replaces itself with the same url string as old one had before. How can I upload a new image every time the same user takes and uploads the photo?
Here is the code
let storage = Storage.storage().reference()
guard let imageData = ecoImage.jpegData(compressionQuality: 0.75) else {
return
}
let metaData = StorageMetadata()
metaData.contentType = "image/jpg"
storage.child("pins/\("Eco1")").putData(imageData, metadata: metaData) { (meta, error) in
if let err = error {
print(err.localizedDescription)
} else {
storage.child("pins/\("Eco1")").downloadURL { (url, err) in
if let e = err {
print(e.localizedDescription)
} else {
let urlString = url?.absoluteString
doc.setData(["uid": uid, "pinLocation": ecoPin, "time": timeData, "url": urlString!]) { (error) in
if error != nil {
print(error?.localizedDescription)
return
} else {
print("Pin saved")
}
}
}
}
}
print("Image sent")
}
You need to generate the random file name for every time you put the new photo.
In the code you provided, after the metadata, declare the variable with UUID.
let fname = UUID.init().uuidString.replacingOccurences(of: "-", with: "")
storage.child("pins/\(fname))...so on
This will generate the random file name each time you upload, and supposedly not replace your previous photo.

url coming back from firebase storage is nil Swift4

I'm trying to upload an image to firebase storage and get the download url to save it in database, but I get a nil value and the function returns upon checking it. I followed the documentation and solutions from other posts and I can't see where I'm mistaking.
The function is :
func uploadImage(completed: #escaping (Bool) -> (),_ image: UIImage ){
print(" ############## UPLOAD STARTED ###########")
// let stringURL: String?
// guard let uid = Auth.auth().currentUser?.uid else {return} // use the userUid to sign the alert
// Create a root reference
let storageRef = Storage.storage().reference()
// Create a reference to "mountains.jpg"
// let alertsRef = storageRef.child("userAlertImages.jpg")//("user/\(uid)") // change path for userAlertImages path
// Create a reference to 'images/mountains.jpg'
let alertsImagesRef = storageRef.child("Alert Images/userAlertImages.jpg")
// While the file names are the same, the references point to different files
// alertsRef.name == alertsImagesRef.name; // true
// alertsRef.fullPath == alertsImagesRef.fullPath; // false
let imageData = UIImageJPEGRepresentation(image, 0.5)
let metaData = StorageMetadata()
metaData.contentType = " jpeg " // data type
metaData.customMetadata = ["k1": "",
"k2" : " ",
"k3" : "",
"k4" : ""]
alertsImagesRef.putData(imageData! as Data , metadata: metaData) { metaData, error in
if(error != nil){
print(error as Any)
return
}
}
// Fetch the download URL
alertsImagesRef.downloadURL { (url,error) in
guard let downloadURL = url else {
print("########## downloaded url is: \(url) #############")
return
}
NewMapViewController.alertImageURL = (url?.absoluteString) ?? ""
// NewMapViewController.alertImageURL = (downloadURL)
print("######### url is:\(String(describing: url)) #########")
completed(true)
// self.postAlertNotification()
self.tapCounter = 0
self.performSegue(withIdentifier: "chooseIconSegue", sender: self)
}
}
can you see where's the error?
Many thanks.
Thinking about it I realised that downloadURL fetch was actually done before than the image upload was complete, that's because Firebase is asynchronous. So I added a completion block to the uploading part, and in the completion scope I put the downloadURL fetching part.
It's not blinking eyes fast and I will appreciate any suggestion on speeding up things, because this way there is a little lag before the seguegets performed.
It's not annoying at all an I could, and probably should, add a little spinning wheel to show users that the app has not frozen, but I rather just avoid the lag altogether if possible. I left the prints written in hope that this post will help others new to Firebase as I am, giving detailed almost step-by-step guidance, as this kind of answers really helped me before, and I haven't found any on the subject.
Rewritten function is:
func uploadImage(completed: #escaping (Bool) -> (),_ image: UIImage ){
print(" ############## UPLOAD STARTED ###########")
// Create a root reference
let storageRef = Storage.storage().reference()
// Create a reference to Images folder
let alertsImagesRef = storageRef.child("Alert Images")
// Create a reference for new images
let uuid = UUID()
let imageRef = alertsImagesRef.child("userAlertImage \(uuid).jpg")
let imageData = UIImageJPEGRepresentation(image, 0.5)
let metaData = StorageMetadata()
metaData.contentType = " jpeg " // data type
metaData.customMetadata = ["k1": "",
"k2" : " ",
"k3" : "",
"k4" : ""]
imageRef.putData(imageData! as Data , metadata: metaData, completion: { metaData, error in
if(error != nil){
print(error as Any)
return
}
print(" ####### image uploaded #######")
self.tapCounter = 0
self.performSegue(withIdentifier: "chooseIconSegue", sender: self)
// fetch url v2
imageRef.downloadURL(completion: { (url, err) in
if let err = err {
print("Error downloading image file, \(err.localizedDescription)")
return
}
guard let url = url else { return }
//Now you have the download URL for the image, and can do whatever you want with it.
NewMapViewController.alertImageURL = url.absoluteString
print(" ######### url is:\(String(describing: url)) #########")
completed(true)
// self.postAlertNotification()
// self.tapCounter = 0
// self.performSegue(withIdentifier: "chooseIconSegue", sender: self)
print(" ############## UPLOAD ENDED ###########")
})
})
}

ImageView to Firebase Storage in Swift

I am trying to upload an image from ImageView to Firebase storage but it won't work.
I have listed my code below: My image view is called ProfileImage
let storageRef = Storage.storage().reference().child("myImage.png")
if let uploadData = self.ProfileImage.image!.pngData() {
storageRef.putFile(from: uploadData, metadata: nil) { (metadata, error) in
if error != nil {
print("error")
completion(nil)
} else {
// your uploaded photo url.
}
}
}
It comes up with the error "Cannot convert value of type 'Data' to expected argument type 'URL'
You are trying to upload Data, not a file. Replace
putFile
With
putData
And it should work fine
Try this code :-
let storageRef = Storage.storage().reference().child("myImage.png")
if let uploadData = UIImagePNGRepresentation(self.profileImage.image!) {
storageRef.put(uploadData, metadata: nil) { (metadata, error) in
if error != nil {
print("\(error.localizeDescription)")
} else {
// your uploaded photo url.
}
}
let refDatabase = Database.database().reference()
var refstorage = Storage.storage().reference()
let data = image.jpegData(compressionQuality: 1.0) //
let metadata = StorageMetadata()
metadata.contentType = "image/jpeg"
let postkey = refDatabase.child("Post").childByAutoId().key
print(postkey)
let imagename = "PostImage/\(postkey).png"
refstorage = refstorage.child(imagename)
let timestamp = Date().timeIntervalSince1970 // you can use this to track time when it was uploaded.
self.refstorage?.putData(data!, metadata: metadata, completion: { (meta, error) in
if error == nil{
if let imageData = meta?.downloadURL()?.absoluteString{
// DO SOMETHING
} else {
print("Couldn't get the URL for image")
}
}
})
I know that this question has been answered, but there is an easier way to do this. Along with import Firebase and Firebase Storage, you will also have to add FirebaseUI to your podfile and import it.
After you have done that, you could get your image to your app much simpler.
let storage = Storage.storage()
let storageRef = storage.reference()
let placeholderImage = UIImage(named: "placeholder.jpeg")
let reference = storageRef.child("myImage.png")
ProfileImage.sd_setImage(with: reference, placeholderImage: placholderImage)
(The placeholder Image would just be a transparent image that you put in your assets folder in XCode that you could reuse multiple times in your application, for whenever you needed to get a Firebase Image on your app.)

Reference Firebase users in database with images in Firebase Storage Swift

Normally I would be able to find an answer to this question online but since its so new I have been having trouble.
When I have users sign into the app and they choose 4-5 pictures for their profile, how do I store those images in Firebase Storage and reference those images to that user in Firebase Database?
Thanks
You upload them to the Firebase Storage first and then store the url in Firebase Database
let storage = FIRStorage.storage()
let data: NSData = myImageData
let userProfilePic = storageRef.child("users/abc/profileimage.jpg")
let uploadTask = userProfilePic.putData(data, metadata: nil) { metadata, error in
if (error != nil) {
// Uh-oh, an error occurred!
} else {
let downloadURL = metadata!.downloadURL
// store downloadURL in db
storeUserProfileInDB(downloadURL)
}
}
func storeUserProfileInDB(profileImgUrl: NSURL) {
let ref = FIRDatabase.database().reference()
let key = ref.child("users").childByAutoId().key
let dictionaryUser = [ "userName" : name! ,
"imageUrl" : profileImgUrl.absoluteString,
]
let childUpdates = ["/users/\(key)": dictionaryTodo]
ref.updateChildValues(childUpdates, withCompletionBlock: { (error, ref) -> Void in
//save
})
}

Resources