After updating Firebase to Version 4 and correcting all 200 errors, I am left with 2 warnings which make my app crash. I looked up this error and tried the resolution with no success:
storageReference.downloadURLWithCompletion()
I must be doing it wrong:
func setUserInfo(_ user: User!, usersname: String, email: String, password: String, cell: String, data: Data!) {
// Create Path for User Image
let imagePath = "images/riders/\(user.uid)/Profile Pic/userPic.jpg"
// Create image Reference
let imageRef = rDataService.Instance.storageRef.child(imagePath)
// Create Metadata for the image
let metaData = StorageMetadata()
metaData.contentType = "image/jpeg"
// Save the user Image in the Firebase Storage File
imageRef.putData(data as Data, metadata: metaData) { (metaData, error) in
if error == nil {
let changeRequest = user.createProfileChangeRequest()
changeRequest.displayName = usersname
changeRequest.photoURL = metaData?.downloadURL()
changeRequest.commitChanges(completion: { (error) in
if error == nil {
self.saveUser(user, usersname: usersname, email: email, password: password, cell: cell)
} else {
print(error!.localizedDescription)
}
})
} else {
print(error!.localizedDescription)
}
}
}
The error is happening on this line:
changeRequest.photoURL = metaData?.downloadURL()
Edit
After adjustments, getting warning on this line:
if let profilepicMetaData = profilepicMetaData {
error: Value 'profilepicMetaData' was defined but never used; consider replacing with boolean test
App is still crashing:
// Save the user profile Image in the Firebase Storage File
imageRef.putData(data as Data, metadata: profilepicMetaData) { (profilepicMetaData, error) in
if let profilepicMetaData = profilepicMetaData {
imageRef.downloadURL(completion: { (url, error) in
guard let url = url else {
if let error = error {
print(error)
}
return
}
let changeRequest = user.createProfileChangeRequest()
changeRequest.displayName = usersname
changeRequest.photoURL = url
changeRequest.commitChanges(completion: { (error) in
if error == nil {
self.saveUser(user, usersname: usersname, email: email, password: password, year: year, makeAndModel: makeAndModel, cell: cell, plateNo: plateNo)
} else {
print(error!.localizedDescription)
}
})
})
} else {
print(error!.localizedDescription)
}
}
Crash!
You need to use the original storage reference object imageRef to obtain the download url. (please check the comments through the code):
imageRef.putData(data, metadata: profilepicMetaData) {
// use if let to unwrap the metadata returned to make sure the upload was successful. You can use an underscore to ignore the result
if let _ = $0 {
// start the async method downloadURL to fetch the url of the file uploaded
imageRef.downloadURL {
// unwrap the URL to make sure it is not nil
guard let url = $0 else {
// if the URL is nil unwrap the error, print it
if let error = $1 {
// you can present an alert with the error localised description
print(error)
}
return
}
// your createProfileChangeRequest code needs to be run after the download url method completes. If you place it after the closure it will be run before the async method finishes.
let changeRequest = user.createProfileChangeRequest()
changeRequest.displayName = usersname
changeRequest.photoURL = url
changeRequest.commitChanges {
// unwrap the error and print it
if let error = $0 {
// again you might present an alert with the error
print(error)
} else {
// user was updated successfully
self.saveUser(user, usersname: usersname, email: email, password: password, year: year, makeAndModel: makeAndModel, cell: cell, plateNo: plateNo)
}
}
}
} else if let error = $1 {
// present an alert with the error
print(error)
}
}
Related
enter image description here
#error >> Type of expression is ambiguous without more context.
Auth.auth().createUser(withEmail: email, password: password) { AuthDataResult, error in
// handle error
if let error = error {
print("Failed to create a user with error", error.localizedDescription)
return
}
// set profile image
guard let profileImage = self.plusPhotoButton.imageView?.image else { return }
//upload data
guard let uploadData = profileImage.jpegData(compressionQuality: 0.3) else { return }
// place image in database
let filename = NSUUID().uuidString
let storageRef = Storage.storage().reference().child("profile_image").child(filename)
storageRef.putData(uploadData, metadata: nil, completion: {(metadata, error) in
// handle error
if let error = error {
print("Faild to upload image to firebase storage with error", error.localizedDescription)
}
// profile image URL
guard let profileImageURL = metadata?.downloadURL()?.absoluteString else { return }
//user Id
guard let uid = AuthDataResult?.user.uid else { return }
//guard let fcmToken = messaging.messagin().fcmToken else { return }
let dictionaryValues = ["name": fullName,
"username": username,
"profileImageURL": profileImageURL]
let values = [uid: dictionaryValues]
//save data info to database
Database.database().reference().child("users").updateChildValues(values, withCompletionBlock: { (error, ref) in
print("Successfully created user and saved indformation to database")
})
})
}
i import Firebase but still not working.
class FCMStorage {
var storage: Storage!
init() {
storage = Storage.storage()
}
func storeImage(data: Data?, name: String, completion: #escaping ((String?, Error?)->Void)) {
guard let data = data else {
return
}
let metaData = StorageMetadata()
metaData.contentType = "image/jpeg"
let path = "images/" + name
print("img to store = \(path)")
let ref = storage.reference()
let uploadTask = ref.child(path).putData(data, metadata: metaData) { (metadata, error) in
if error == nil {
print(metadata as Any)
ref.downloadURL(completion: { (url, error) in
completion(url?.absoluteString, error)
})
} else {
print("storeImage error: \(error!)")
}
}
uploadTask.observe(.progress) { (snapshot) in
print((snapshot.progress?.fractionCompleted ?? 0) * 100.0)
if snapshot.status == .success || snapshot.status == .failure {
uploadTask.removeAllObservers(for: .progress)
}
}
}
}
Using the able class I am able to upload the image on firebase successfully and I am able to see the uploaded image on the firebase too but...
When I call downloadURL() method it always giving me the following error
ref.downloadURL(completion: { (url, error) in
completion(url?.absoluteString, error)
})
Error: Failed to retrieve a download URL.
Anyone could help me out on this issue!!
EDIT
When I print metadata of the file it prints the following....
FIRStorageMetadata 0x283f99860: {
bucket = "sportoilic.appspot.com";
contentDisposition = "inline; filename*=utf-8''8E13A816-FAF1-47ED-8F84-94BBB8C4C77F";
contentEncoding = identity;
contentType = "application/octet-stream";
generation = 1601287237056536;
md5Hash = "VgMH6NMPGJT//LCD8goaDA==";
metageneration = 1;
name = "8E13A816-FAF1-47ED-8F84-94BBB8C4C77F";
size = 114787;
timeCreated = "2020-09-28T10:00:37.056Z";
updated = "2020-09-28T10:00:37.056Z";
}
and after that when I try to get the download url for the uploaded image< I am getting the above mentioned error(Error: Failed to retrieve a download URL).
What is the issue? Am I missing something here?
After trial and error of several hours, finally got the solution.
class FCMStorage {
var ref: StorageReference!
init(path: String) {
ref = Storage.storage().reference(withPath: path)
print("img to store at path = \(path)")
}
func storeImage(data: Data?, completion: #escaping ((String?, Error?)->Void)) {
guard let data = data else {
return
}
let uploadTask = ref.putData(data, metadata: nil) { (metadata, error) in
if error == nil {
print(metadata as Any)
self.ref.downloadURL(completion: { (url, error) in
completion(url?.absoluteString, error)
})
} else {
print("storeImage error: \(error!)")
}
}
uploadTask.observe(.progress) { (snapshot) in
print((snapshot.progress?.fractionCompleted ?? 0) * 100.0)
if snapshot.status == .success || snapshot.status == .failure {
uploadTask.removeAllObservers(for: .progress)
}
}
}
}
So store image just need to call like this...
let path = "images/" + UUID().uuidString
FCMStorage(path: path).storeImage(data: data) { (imgUrl, error) in
if let strUrl = imgUrl, error == nil {
FCMDatabase.init(OfTable: .chatRooms).setImageUrl(strUrl: strUrl, teamId: self.team?.id ?? 0, forMsgId: self.arrChats.last?.messageId ?? "") { (isDone) in
if isDone {
print("Image url set in database")
}
}
} else {
print("Error: \(error?.localizedDescription ?? "Error while getting image url")")
}
}
I am trying to add an image to Firebase storage and then get the location of that image and store it in a Firestore document with the other user profile data. I am trying to do all of this when the user first creates an account. I was trying to use the image URL to do this but that does not appear to be working. When I run the code below it will sign up a new user and add the photo to Firebase storage but no document gets created in the Firestore database. What am I doing wrong?
#objc func handleSignUp() {
//Signup properties
guard let email = emailTextField.text else { return }
guard let password = passwordTextField.text else { return }
guard let fullName = fullNameTextField.text else { return }
guard let username = usernameTextField.text?.lowercased() else { return }
createUser(email: email,
password: password,
fullName: fullName,
userName: username)
}
func createUser(email: String, password: String, fullName: String, userName: String) {
Auth.auth().createUser(withEmail: email, password: password) { (authResult, error) in
//Handle error
if let error = error {
print("DEBUG: Failed to create user with error: ", error.localizedDescription)
return
}
guard let profileImg = self.plusPhotoBtn.imageView?.image else { return }
guard let uploadData = profileImg.jpegData(compressionQuality: 0.3) else { return }
let userID = Auth.auth().currentUser!.uid
let filename = NSUUID().uuidString
//Storage location for photo in Firebase
let storageRef = Storage.storage().reference().child("profile_images").child(userID).child(filename)
storageRef.putData(uploadData, metadata: nil, completion: { (metadata, error) in
//Handle error
if let error = error {
print("Failed to upload image to Firebase Storage with error", error.localizedDescription)
return
}
guard metadata != nil else { return }
let path = storageRef.fullPath;
guard let username = self.usernameTextField.text?.lowercased() else { return }
storageRef.downloadURL { (url, _) in
let data = ["name": fullName,
"username": username,
"profileImagePath": path,
"email" : email] as [String : Any]
self.addDocument(userData: data)
}
})
}
}
I think you should add the "downloadURL" part inside the "putData".
After completion of the put data process you should try to get the URL or else it will fail.
Try this and see if it works:
#objc func handleSignUp() {
//Signup properties
guard let email = email.text else { return }
guard let password = password.text else { return }
guard let fullName = name.text else { return }
guard let username = name.text?.lowercased() else { return }
createUser(email: email,
password: password,
fullName: fullName,
userName: username)
}
func createUser(email: String, password: String, fullName: String, userName: String) {
Auth.auth().createUser(withEmail: email, password: password) { (authResult, error) in
//Handle error
if let error = error {
print("DEBUG: Failed to create user with error: ", error.localizedDescription)
return
}
guard let profileImg = self.plusPhotoBtn.imageView?.image else { return }
guard let uploadData = profileImg.jpegData(compressionQuality: 0.3) else { return }
let filename = NSUUID().uuidString
//Storage location for photo in Firebase
let storageRef = Storage.storage().reference().child("profile_images").child(filename)
storageRef.putData(uploadData, metadata: nil, completion: { (metadata, error) in
//Handle error
if let error = error {
print("Failed to upload image to Firebase Storage with error", error.localizedDescription)
return
}
guard let metadata = metadata else { return }
guard let username = self.usernameTextField.text?.lowercased() else { return }
storageRef.downloadURL { (url, _) in
guard let downloadURL = url else {
print("DEBUG: Profile image url is nil")
return
}
let data = ["name": fullName,
"username": username,
"profileImageUrl": downloadURL,
"email" : email]
self.addDocument(userData: data)
}
})
}
}
func addDocument(userData: [String: Any]) {
Firestore.firestore().collection("profile_data").addDocument(data: userData) { (err) in
if let err = err {
debugPrint("Error adding document: \(err)")
} else {
self.navigationController?.popViewController(animated: true)
}
}
}
I want to upload an array of images by user
This is the code when i am uploading a single image to firebase storage refer Code: 1. but i am having problem when i am uploading and array of image. Refer code: 2
Code: 1
func uploadImage(image:UIImage,userId:String,completion:#escaping(_ status:Bool,_ response:String)->Void){
// if status is true then downloadurl will be in response
// Data in memory
guard let data = image.jpegData(compressionQuality: 0.2) else{
completion(false,"Unable to get data from image")
return
}
// Create a reference to the file you want to upload
let riversRef = firebaseStorage.reference().child("images/\(userId).jpg")
// Upload the file to the path "images/rivers.jpg"
let _ = riversRef.putData(data, metadata: nil) { (metadata, error) in
guard let _ = metadata else{
// Uh-oh, an error occurred!
completion(false,error!.localizedDescription)
return
}
// You can also access to download URL after upload.
riversRef.downloadURL { (url, error) in
guard let downloadURL = url else{
// Uh-oh, an error occurred!
completion(false,error!.localizedDescription)
return
}
completion(true,downloadURL.absoluteString)
}
}
}
Code: 2
func uploadPostImages(image:[UIImage],userId:String,completion:#escaping(_ status:Bool,_ response:String)->Void){
let photoDictionary = ["postImages": PostArray.sharedInstance.photosArray as NSArray]
// Create a reference to the file you want to upload
let riversRef = firebaseStorage.reference().child("postImages/\(userId).jpg")
// Upload the file to the path "images/rivers.jpg"
let _ = riversRef.putData(photoDictionary, metadata: nil) { (metadata, error) in
guard let _ = metadata else{
// Uh-oh, an error occurred!
completion(false,error!.localizedDescription)
return
}
// You can also access to download URL after upload.
riversRef.downloadURL { (url, error) in
guard let downloadURL = url else{
// Uh-oh, an error occurred!
completion(false,error!.localizedDescription)
return
}
completion(true,downloadURL.absoluteString)
}
}
}
I am getting this error in code: 2
Cannot convert value of type '[String : NSArray]' to expected argument type 'Data'
anyone who can help me??
To my knowledge something like this should work. Sorry, I am not at an IDE. If this doesn't work leave a comment and I will take a closer look.
func uploadImages(
images:[UIImage],
userId:String,
completion:#escaping(_ status:Bool,_ response:String)->Void)
{
images.enumerated().forEach { (index, image) in
guard let data = image.jpegData(compressionQuality: 0.2) else{
completion(false,"Unable to get data from image")
return
}
// Create a reference to the file you want to upload
let riversRef = firebaseStorage.reference().child("images/\(userId)\(index).jpg")
// Upload the file to the path "images/rivers.jpg"
let _ = riversRef.putData(data, metadata: nil) { (metadata, error) in
guard let _ = metadata else{
// Uh-oh, an error occurred!
completion(false,error!.localizedDescription)
return
}
// You can also access to download URL after upload.
riversRef.downloadURL { (url, error) in
guard let downloadURL = url else{
// Uh-oh, an error occurred!
completion(false,error!.localizedDescription)
return
}
completion(true,downloadURL.absoluteString)
}
}
}
}
This code will upload an array of images
func uploadImages(images:[UIImage], userId:String, completion:#escaping(_ status:Bool, _ response:String)->Void){
guard images.count <= 5 && !images.isEmpty else {return}
convertImagesToData(images: images).enumerated().forEach { (index, image) in
let riversRef = firebaseStorage.reference().child("userAdPostImages/\(userId)\(index).jpg")
let _ = riversRef.putData(image, metadata: nil, completion: { (_ , error) in
if let error = error {
completion(true, error.localizedDescription)
return
}
riversRef.downloadURL(completion: {(url, error) in
if let error = error{
completion(true,error.localizedDescription)
return
}
guard let downloadURL = url else {return}
completion(false,downloadURL.absoluteString)
})
})
}
}
and this will add your post to firebase including array images
func addPost() {
hud.show()
dispatchGroup.enter()
let user = Auth.auth().currentUser
if let user = user {
self.userId = user.uid
}
let location = self.locationTextfield.text ?? ""
let type = self.selectedType ?? ""
let description = self.descriptionTextView.text ?? ""
let adImages = self.adImages
let userId = self.userId ?? ""
let price = priceTextfield.text ?? ""
let userName = self.userName ?? ""
let userProfileImage = self.userProfileImage ?? ""
let userphoneNumber = self.phoneNumber ?? ""
ServerManager.sharedDelegate.uploadImages(images: adImages, userId: userId) { (isError, urlString) in
guard !isError else {
DispatchQueue.main.async {
hud.hide()
}
return
}
self.imagePaths.append(urlString)
print("This is the image path saved \(self.imagePaths)!!!!")
if self.imagePaths.count == adImages.count {
self.dispatchGroup.leave()
}
}
dispatchGroup.notify(queue: .global(qos: .background), execute: {
ServerManager.sharedDelegate.addPost(UserProfileImage: userProfileImage, UserName: userName, phoneNumber: userphoneNumber, adImages: self.imagePaths, Price: price, location: location, type: type, Description: description) { _ , message in
DispatchQueue.main.async {
hud.hide()
self.vc?.view.makeToast(message)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.vc?.dismiss(animated: true, completion: nil)
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "load"), object: nil)
}
}
return
}
})
}
Thanks to #MichaelWells for help and sorry for late posting the answer
You can encode the dict and then store it as data:
let data = JSONEncoder().encode(photoDictionary)
I am trying to implement a user profile view, in which the user chooses a profile image from camera roll and a username. When everything is chosen, the user clicks on a createProfileButton that calls the following function:
#objc func createProfileButtonPressed() {
let user = Auth.auth().currentUser
let uid = Auth.auth().currentUser?.uid
let firebaseStorageRef = Storage.storage().reference().child("profile_picture").child(uid!)
let database = Firestore.firestore()
// settings for disabling warning concerning firestore
let settings = database.settings
settings.areTimestampsInSnapshotsEnabled = true
database.settings = settings
let profileChangeRequest = user?.createProfileChangeRequest()
profileChangeRequest?.displayName = self.usernameTextfield.text
if let profileImage = profilePicture, let imageData = UIImageJPEGRepresentation(profileImage, 0.1) {
firebaseStorageRef.putData(imageData, metadata: nil) { (metadata, error) in
if error != nil {
print("Error:", error!)
return
}
firebaseStorageRef.downloadURL(completion: { (url, error) in
if error != nil {
print("Error:", error!)
return
}
print("Picture URL:", url!)
profileChangeRequest?.photoURL = url
})
}
}
profileChangeRequest?.commitChanges(completion: { (error) in
if error != nil {
print("Error:", error!)
return
}
print("Profile created")
var databaseRef = database.collection("users").addDocument(data: ["uid": uid!, "email": user?.email!, "username": user?.displayName, "profileImageURL": user?.photoURL?.absoluteString], completion: { (error) in
if error != nil {
print("Error storing user", error!)
return
}
print("user successfully stored!")
})
self.navigationController?.popToRootViewController(animated: true)
})
}
I want the users email, username, uid and photoURL stored in a firestore document. However, what I get is this error:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid call to setPhotoURL: after commitChangesWithCallback:.'
This happens after the popToRootViewController function gets called. Why does this error message show up although I want to set the photoURL before I commit the changes?
Just to expand on ekscrypto's comment. Your call to profileChangeRequest?.photoURL = url is taking place within the download callback, thus it going to be run after your call to commitChanges.
You need to put your commitChanges block inside your callback -
if let profileImage = profilePicture, let imageData = UIImageJPEGRepresentation(profileImage, 0.1) {
firebaseStorageRef.putData(imageData, metadata: nil) { (metadata, error) in
if error != nil {
print("Error:", error!)
return
}
firebaseStorageRef.downloadURL(completion: { (url, error) in
if error != nil {
print("Error:", error!)
return
}
print("Picture URL:", url!)
profileChangeRequest?.photoURL = url
profileChangeRequest?.commitChanges(completion: { (error) in
if error != nil {
print("Error:", error!)
return
}
print("Profile created")
var databaseRef = database.collection("users").addDocument(data: ["uid": uid!, "email": user?.email!, "username": user?.displayName, "profileImageURL": user?.photoURL?.absoluteString], completion: { (error) in
if error != nil {
print("Error storing user", error!)
return
}
print("user successfully stored!")
})
self.navigationController?.popToRootViewController(animated: true)
})
})
}
}