I have an array of image data(in bytes), and I want to upload them to storage and get the download URL for all of them. But after uploading lots of pictures, there is only one file appears in the storage under the reference. I do get the download URLs, but only the last one works, the rest all say access denied.
I suspect it is because I am putting all the pictures under the same storage reference and the picture substitutes every time. But what can I do to upload multiple images?
My upload code:
var picturesURL: [String] = [] // I want to put the download URL in this array
if self.imageButton.isSelected{
picturesURL = []
for photoData in self.pictures{ // pictures is the array that stores all the photo data
self.storage.child("images/file.png").putData(photoData, metadata: nil) { _, error in
guard error == nil else{
print("Failed to upload")
return
}
self.storage.child("images/file.png").downloadURL (completion: { url, error in
guard let url = url, error == nil else{
print("problems getting url")
return
}
let urlString = url.absoluteString
picturesURL.append(urlString)
print(urlString)
//UserDefaults.standard.set(urlString, forKey: "url")
})
}
}
Related
I currently have a view controller where the user uploads an image and that image is stored in the Firebase Storage, with folders of their user id and in there their uploaded image. I want to fetch that image's url and display it on the view controller. The question I have is, do i need to store that uploaded image's download url into the realtime database ie; Users - > User Id - > Media -> Image Title -> Download URL? Or is there a way for me to refer an image view to Firebase storage and accordingly into the User Id's folder containing all the images and display this? Would appreciate it a lot if someone could help me out. Thank you!
This is my code:
let imageName = (Auth.auth().currentUser?.uid)!+"/\(imageTitle.text!)"
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
}
}
})
}
There are two ways to read the data from Storage:
Through the Firebase SDK by calling getData or write on a reference, as shown in the documentation on downloading data.
Through a download URL for the reference, which provides public read-only access.
To call getData, write or to get a download URL, you need to have a reference to the file as shown in creating a reference.
Since you store the files under the UID of the user, you can always create a reference to any for for a user if you known their UID. So you can always perform one of the two methods above to read the data for the file, even if you didn't store the download URL.
That said, it is fairly common to store the download URL in a database, as it means you can then treat it like any other image URL. By doing so the rest of your code won't have to know anything about Cloud Storage.
So: it can work without storing the download URL in the database, but it's also fine (and common) if you do store the download URL. The choice is really up to you.
I have seen posts and read the docs on how to upload a file with a url reference to local disk, however, I cannot find anything about how to do this with a file in memory? The file is an array of pdfs. I take it I have to have some kind of URL reference to that file. Where would I URL reference a pdf in memory? Anybody know?
Edit:
I used this code from the Docs, however, I'm getting the following error for putData: Type of expression is ambiguous without more context
Which I think means the constructor is not expecting a type of PDFDoc?
#IBAction func confirmUploadButtonTapped(_ sender: Any) {
let uuid = UUID().uuidString
// Create a root reference
let storage = Storage.storage()
// Create a storage reference from our storage service
let storageRef = storage.reference()
// Data in memory
let data = Data()
// Create a reference to the file you want to upload
let pdfRef = storageRef.child("docRequest/" + uuid + "/" + ".pdf")
// Upload the file to the path "images/rivers.jpg"
let uploadTask = pdfRef.putData(pdfDocument, metadata: nil) { (metadata, error) in
guard let metadata = metadata else {
// Uh-oh, an error occurred!
print(error)
return
}
// Metadata contains file metadata such as size, content-type.
let size = metadata.size
// You can also access to download URL after upload.
pdfRef.downloadURL { (url, error) in
guard let downloadURL = url else {
// Uh-oh, an error occurred!
print(error)
return
}
}
}
If you have the data for the PDF in memory already, you can upload that data to Firebase Storage by calling putData. See for an example the documentation on Upload from data in memory, or this code in the Firebase quickstart project.
This line is the issue
let pdfRef = storageRef.child("docRequest/" + uuid + "/" + ".pdf")
because it will create a path of
docRequest/xxxxx/.pdf
So you should use this
let pdfRef = storageRef.child("docRequest/" + uuid + ".pdf")
when will be a path of
docRequest/xxxxx.pdf
Also, please ensure that when that file is written to storage, you also save the url in Firebase so you can get to it later.
As far as a PDF in memory, the PDFKit (which I think you're using) has functions to work with that, check into pdfData(actions:) renderer to produce a Data object
I have little problem. I got caching to work with the following URL:
let URL = NSURL(string: "https://raw.githubusercontent.com/onevcat/Kingfisher/master/images/kingfisher-\(indexPath.row + 1).jpg")!
But can't get it to work like this with this URL:
FIRStorage.storage().reference().child("\(productImageref!).png").downloadURLWithCompletion({(url, error)in
if error != nil{
print(error)
return
}else{
cell.snusProductImageView.kf_setImageWithURL(url , placeholderImage: nil,
optionsInfo: [.Transition(ImageTransition.Fade(1))],
progressBlock: { receivedSize, totalSize in
print("\(indexPath.row + 1): \(receivedSize)/\(totalSize)")
},
completionHandler: { image, error, cacheType, imageURL in
print("\(indexPath.row + 1): Finished")
})
}
})
What I am doing wrong here? Can you point me to the right direction? For caching I use the 3rd party library "KingFisher"
Edit: Firebase guy Mike McDonald's quote
"The Github one has Cache-Control: max-age=300 while Firebase Storage
doesn't have cache control set by default (you can set it when you
upload the file, or change it by updating metadata), so I assume
that's why KingFisher isn't caching it."
KingFisher owner quotes.
Like Firebase guy Mike McDonald said I directly set the cache-control when uploading the image to the Firebase storage. Then it will be cached correctly when loading it later.
(Configuration: Swift3, Firebase Storage 3.11.0)
let storage = FIRStorage.storage()
let storageRef = storage.reference(forURL: yourFirebaseStorageReferenceURL)
let imageRef = storageRef.child("profileImages/\(imageUUID).jpg")
let metadata = FIRStorageMetadata()
metadata.contentType = "image/jpeg"
// THIS is the important part
metadata.cacheControl = "public,max-age=2592000"
let uploadTask = imageRef.put(imageData, metadata: metadata) { (metadata, error) in
if let error = error
{
completion(.failure(error as NSError))
}
guard let metadata = metadata, let downloadURL = metadata.downloadURL()?.absoluteString else {
// Handle upload error
return
}
// Handle successful upload
}
When I download the image, the cache-control is set
The way I got them caching was that I had to write all image url-s into JSON like this:
.setValue(url!.absoluteString)
And then read those as url-s
I am making a function to retrieve the url as the user Image. However, my upload image name function created by NSUUID. Therefore, I would not know what is the name of each user profile picture. How could I improve my code to get the user imgae for every user instead of hard coding the img name?
func getUserProfilePic(){
let uid = FIRAuth.auth()?.currentUser?.uid
let profilePath = refStorage.child("\(uid)").child("profilePic/xxxxx.jpg") // xxxxx = NSUUID
profilePath.dataWithMaxSize(1 * 1024 * 1024) { (data, error) -> Void in
if (error != nil) {
print("got no pic")
} else {
let profilePic: UIImage! = UIImage(data: data!)
self.imageV.image = profilePic
print("got pic")
}
}
}
The path is uid/profilePic/--<-file name->--
upload function
func uploadingPhoto(){
let uid = FIRAuth.auth()?.currentUser?.uid
let imgName = NSUUID().UUIDString + ".jpg"
let filePath = refStorage.child("\(uid!)/profilePic/\(imgName)")
var imageData = NSData()
imageData = UIImageJPEGRepresentation(profilePic.image!, 0.8)!
let metaData = FIRStorageMetadata()
metaData.contentType = "image/jpg"
let uploadTask = filePath.putData(imageData, metadata: metaData){(metaData,error) in
if let error = error {
print(error.localizedDescription)
return
}else{
let downloadURL = metaData!.downloadURL()!.absoluteString
let uid = FIRAuth.auth()?.currentUser?.uid
let userRef = self.dataRef.child("user").child(uid!)
userRef.updateChildValues(["photoUrl": downloadURL])
print("alter the new profile pic and upload success")
}
}
I highly recommend using Firebase Storage and the Firebase Realtime Database together to store that UUID -> URL mapping, and to "list" files. The Realtime Database will handle offline use cases like Core Data as well, so there's really no reason to futz with Core Data or NSUserDefaults. Some code to show how these pieces interact is below:
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
// 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 downloaded 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.
There can be two ways to go about this :-
1.) Store the Firebase Storage path of users profile_picture in your Firebase database and retrieve every time before you start downloading your profile picture.
2.) Store the file path in CoreData every time your user uploads a profile picture and hit that path every time to get file_Path to where you stored that user's profile_pic .
Storing the path :-
func uploadSuccess(metadata : FIRStorageMetadata , storagePath : String)
{
print("upload succeded!")
print(storagePath)
NSUserDefaults.standardUserDefaults().setObject(storagePath, forKey: "storagePath.\((FIRAuth.auth()?.currentUser?.uid)!)")
//Setting storagePath : (file path of your profile pic in firebase) to a unique key for every user "storagePath.\((FIRAuth.auth()?.currentUser?.uid)!)"
NSUserDefaults.standardUserDefaults().synchronize()
}
Retrieving your path, Every time you start downloading your image :-
let storagePathForProfilePic = NSUserDefaults.standardUserDefaults().objectForKey("storagePath.\((FIRAuth.auth()?.currentUser?.uid)!)") as? String
Note :- i am using currentUser ID, you can use USER SPECIFIC id ,if you want to download multiple users profile pics, all you need to do is put their uid in place.
unable to get access to my metadata on the new Firebase. Able to get download of article and have them display.
storageRef.child(article).metadataWithCompletion { (metadata, error) in
if error != nil{
print("error getting metadata")
}else{
let metadata1 = FIRStorageMetadata()
let nameMeta = metadata1.downloadURLs
print("nameMeta is \(nameMeta)")
}
output display:
nameMeta is nil
You shouldn't create a new metatada instance, you should use the one already provided in the closure.
else {
let downloadUrl = metadata.downloadUrl()
print(downloadUrl)
}