Learn Courses My Dashboard

Value of type 'StorageMetadata' has no member 'downloadURL'

Hello,

In creating my user profile in app, I am getting this error below. It’s the only error. Any clue how to fix it?

class SignUpViewController:UIViewController, UITextFieldDelegate {
    
    
    @IBOutlet var imageView: UIImageView!
    @IBOutlet var uploadBtn: UIButton!
    @IBOutlet var retrieveImages = [UIImage]()
    
    @IBOutlet var firstNameTextField: UITextField!
    @IBOutlet var lastNameTextField: UITextField!
    @IBOutlet var emailTextField: UITextField!
    @IBOutlet var passwordTextField: UITextField!
    @IBOutlet var signUpButton: UIButton!
    @IBOutlet var errorLabel: UILabel!
    var imagePicker: UIImagePickerController!
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setUpElements()
        
        func setUpElements() {
            
            // Hide the error label
            errorLabel.alpha = 0
            
            // Style the elements
            Utilities.styleTextField(firstNameTextField)
            Utilities.styleTextField(lastNameTextField)
            Utilities.styleTextField(emailTextField)
            Utilities.styleTextField(passwordTextField)
            Utilities.styleFilledButton(signUpButton)
            
            // Profile image
            imageView.layer.cornerRadius = imageView.bounds.height / 2
            imageView.layer.borderWidth = 12
            imageView.layer.borderColor = UIColor.white.cgColor
            imageView.clipsToBounds = true
            
            
        }
        
    }
    
    // Photo gallery
    @IBAction func imagePickerBtn(_ sender: Any) {
        let picker = UIImagePickerController()
        picker.allowsEditing = true
        picker.delegate = self
        present(picker, animated: true)
        
    }
    
    // Dismiss our view
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        guard let imageData = info[.editedImage] as? UIImage else {return}
        imageView.image = imageData

        dismiss(animated: true)
        
    }
    
    // Task a selfie
    @IBAction func camerPictureBtn(_ sender: Any) {
        
        let photoTaker = UIImagePickerController()
        photoTaker.sourceType = .camera
        photoTaker.allowsEditing = true
        photoTaker.delegate = self
        present(photoTaker, animated: true)
    }
    
    // dismiss the picker
    func imagePickerControllerDidCancel(_ photoTaker: UIImagePickerController) {
        photoTaker.dismiss(animated: true, completion: nil)
    }
    

    func validateFields() -> String? {
        
        // 2. Check that all fields are filled in
        if firstNameTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" ||
            lastNameTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" ||
            emailTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" ||
            passwordTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" {
            
            return "Please fill in all fields."
        }
        
        // 3. Check if the password is secure
        
        let cleanedPassword = passwordTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
        
        if Utilities.isPasswordValid(cleanedPassword) == false {
            // 4.Password isn't secure enough
            return "Please make sure your password is at least 8 characters, contains a  special character and a number."
            
        }
        
        return nil
    }
    
    /**
     Enables or Disables the **continueButton**.
     */
    
    @objc func handleSignUp() {
        guard let firstName = firstNameTextField.text else { return }
        guard let lastName = lastNameTextField.text else { return }
        guard let email = emailTextField.text else { return }
        guard let pass = passwordTextField.text else { return }
        guard let image = imageView.image else { return }
        
//        setContinueButton(enabled: false)
//        continueButton.setTitle("", for: .normal)
//        activityView.startAnimating()
        
        Auth.auth().createUser(withEmail: email, password: pass) { user, error in
            if error == nil && user != nil {
                print("User created!")
                
                // 1. Upload the profile image to Firebase Storage
                
                self.uploadProfileImage(image) { url in
                    
                    if url != nil {
                        let changeRequest = Auth.auth().currentUser?.createProfileChangeRequest()
                        changeRequest?.displayName = firstName
                        changeRequest?.displayName = lastName
                        changeRequest?.photoURL = url
                        
                        changeRequest?.commitChanges { error in
                            if error == nil {
                                print("User display name changed!")
                                
                                self.saveProfile(username: firstName, profileImageURL: url!) { success in
                                    if success {
                                        self.dismiss(animated: true, completion: nil)
                                    }
                                }
                                
                            } else {
                                print("Error: \(error!.localizedDescription)")
                            }
                        }
                    } else {
                        // Error unable to upload profile image
                    }
                    
                }
                
            } else {
                print("Error: \(error!.localizedDescription)")
            }
        }
    }
    
    
    func uploadProfileImage(_ image:UIImage, completion: @escaping ((_ url:URL?)->())) {
        guard let uid = Auth.auth().currentUser?.uid else { return }
        let storageRef = Storage.storage().reference().child("user/\(uid)")
        
        guard let imageData = image.jpegData(compressionQuality: 0.75) else { return }
        
        
        let metaData = StorageMetadata()
        metaData.contentType = "image/jpg"
        
        storageRef.putData(imageData, metadata: metaData) { metaData, error in
            if error == nil, metaData != nil {
                if let url = metaData?.downloadURL() {
                    completion(url)
                } else {
                    completion(nil)
                }
                // success!
            } else {
                // failed
                completion(nil)
            }
        }
    }
    
    func saveProfile(username:String, profileImageURL:URL, completion: @escaping ((_ success:Bool)->())) {
        guard let uid = Auth.auth().currentUser?.uid else { return }
        let databaseRef = Database.database().reference().child("users/profile/\(uid)")
        
        let userObject = [
            "username": username,
            "photoURL": profileImageURL.absoluteString
        ] as [String:Any]
        
        databaseRef.setValue(userObject) { error, ref in
            completion(error == nil)
        }
    }
}

extension SignUpViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    
//    private func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
//        picker.dismiss(animated: true, completion: nil)
//    }
    
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
        
        if let pickedImage = info[UIImagePickerController.InfoKey.editedImage.rawValue] as? UIImage {
            self.imageView.image = pickedImage
        }
        
        picker.dismiss(animated: true, completion: nil)
    }
    
    
}


What is the definition of your StorageMetadata struct or class?

Well I am looking at this doc: https://firebase.google.com/docs/reference/android/com/google/firebase/storage/StorageMetadata

I don’t see a downloadURL method in that API documentation. Which is why you get that error.

Drilling down a bit, I do see that StorageMetadata has a property called storageReference that gets you to a StorageReference instance (probably the same one you have as storageRef a couple lines above). And StorageReference has a method called downloadURL() which is async and throws errors.

I’m afraid I know next to nothing about Firebase but seems to me you are trying to use downloadURL() incorrectly and need to take a second look at the documentation or whatever tutorial you may have been following.

1 Like

I’ve never seen anyone specify metadata when saving an image to Firebase storage so from my point of view you need not bother with that. Just make it simple.
Your uploadProfileImage function signature looks a little bit odd so with that in mind try this:

    func uploadProfileImage(image: UIImage, completion: @escaping (URL?) -> Void) {

        // Extract user id to use in file path
        guard let uid = Auth.auth().currentUser?.uid else { return }

        // Create storage reference
        let storageRef = Storage.storage().reference()

        // Turn our image into data
        let imageData = image.jpegData(compressionQuality: 0.75)

        // Check that we were able to convert it to data
        guard imageData != nil else { return }

        // Specify the file path and name
        let path = "users/\(uid).jpg"
        let fileRef = storageRef.child(path)

        fileRef.putData(imageData!, metadata: nil) { metadata, error in

            //  Check for errors
            if error == nil && metadata != nil {
                //  Get the url for the image in storage
                fileRef.downloadURL { url, error in
                    //  Check for errors
                    if url != nil && error == nil {
                        // return the url
                        completion(url)
                    }
                }
            }
        }
    }

When you call that function, the return data needs to be unwrapped in the event that there is no url returned.

For example:

    func displayUrl() {
        uploadProfileImage(image: UIImage(named: "TajMahal2")!) { url in
            if let url = url {
                print(url)
            }
        }
    }
1 Like

Your right. This was from someone’s tutorial. I’m going to use what I had before. It was simpler. What I was really trying to do is to see how I can properly save my user’s profile pictures when they sign up on my app. When I save my file as “profilePic” is shows up, but when another user signs up it updates the same file for all users. I need the user profile images to be unique to each user. I’m failing to do so. I’ll leave some snippets here to see what you think.

This is at the SignUpViewController class

@IBAction func signUpTapped(_ sender: Any) {

                // Make sure the selected image property isn't nil
                guard imageView.image != nil else {
                    return
                }
                
                // Create storage reference
                //guard let uid = Auth.auth().currentUser?.uid else { return }
                let storageRef = Storage.storage().reference().child("user/")
                
                //Turn image into data–
                let imageData = imageView.image!.jpegData(compressionQuality: 0.8)
                
                // Check that we are able to convert it to data
                guard imageData != nil else {
                    return
                }
                
                // Specify file path and name
                //let path = "images/profile\(UUID().uuidString).jpg"
                guard let uid = Auth.auth().currentUser?.uid else { return }
                let path = "\(uid)"
            
                let fileRef = storageRef.child(path)
                // Upload that data
                _ = fileRef.putData(imageData!, metadata: nil) { metadata, error in
                    
                    // Check for error
                    if error == nil && metadata != nil {
                        
                        // Save a reference to that file in Firestore DB
                        let db = Firestore.firestore()
                        guard let uid = Auth.auth().currentUser?.uid else { return }

                        db.collection("users/profile/\(uid))").document().setData(["url": path], completion: { error in

                            // If there were no errors, display the new image
                            if error == nil {

                                DispatchQueue.main.async {
                                    // Add the uploded image to the list of images for display
                                    self.retrieveImages.append(self.imageView.image!)
                                }
                            }
                        })
                    }
                }

This is from a view controller that should display the saved profile picture.

func downloadimages() {
                   guard let uid = Auth.auth().currentUser?.uid else { return }
                   let reference = Storage.storage().reference(withPath: "user/\(uid)")

                    reference.downloadURL { (URL, error) in
                        let data = NSData(contentsOf: URL!)
                        let image = UIImage(data: data! as Data)
                        guard (image?.jpegData(compressionQuality: 0.5)) != nil else { return }
                        cellImg.image = image
                    }
                }
                

I would assume that you are saving that Firebase storage image url that the function uploadProfileImage() returns to you. Is that the case?

Yes sir.

@coder3000

Does your downloadimages function work for you?

Yes everything works. It’s just that when I try to use uid.l with the images I’m not sure how target newly generated file for display.

@coder3000

As I understand it, when the user signs up you save the profile image to storage and also save that url in a relevant collection in Firestore.

When the menu is presented you should query that collection to retrieve the url just as you would for a user that has already signed up and then logs in again.

I am assuming that you are using the currentUser.uid wherever you need to identify the Firestore records that relate to that user in order to retrieve the profile image url to access the image in storage.

How would I store the image url properly in a collection. I am getting an error:

guard let profilePic = self.imageView.image else { return }
                    
                    // 11. User created successfully, now store the fist name and last name and photoURL
                    let db = Firestore.firestore()
                   
                    db.collection("users").addDocument(data: [
                        "firstName": firstName,
                        "lastName": lastName,
                        "photoURL": profilePic.absoluteString,
                        "uid": result!.user.uid]) { (error) in
                        

@coder3000

Are you doing this at the same time as the user is signing up and selecting their profile image?

It’s late here so I will respond to your answer tomorrow my morning my time.

Yes sir. At the same time. Once they tap the Signup button every thing gets uploaded.

To help me figure out what code you should have can you post the code that you have so far that deals with all of the sign up activity?
I’ll get back to you after I’ve had some sleep. :zzz:

import FirebaseFirestore
import FirebaseStorage
import UIKit
import FirebaseAuth
import FirebaseFirestore
import FirebaseCore

class SignUpViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate{

    // Profile Picture
    @IBOutlet var imageView: UIImageView!
    @IBOutlet var uploadBtn: UIButton!
    @IBOutlet var retrieveImages = [UIImage]()
    
    @IBOutlet var firstNameTextField: UITextField!
    @IBOutlet var lastNameTextField: UITextField!
    @IBOutlet var emailTextField: UITextField!
    @IBOutlet var passwordTextField: UITextField!
    @IBOutlet var signUpButton: UIButton!
    @IBOutlet var errorLabel: UILabel!
    
    var imagePicker: UIImagePickerController!

    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setUpElements()
        
        func setUpElements() {
            
            // Hide the error label 
            errorLabel.alpha = 0
            
            // Style the elements
            Utilities.styleTextField(firstNameTextField)
            Utilities.styleTextField(lastNameTextField)
            Utilities.styleTextField(emailTextField)
            Utilities.styleTextField(passwordTextField)
            Utilities.styleFilledButton(signUpButton)
            
            // Profile image
            imageView.layer.cornerRadius = imageView.bounds.height / 2
            imageView.layer.borderWidth = 12
            imageView.layer.borderColor = UIColor.white.cgColor
            imageView.clipsToBounds = true
            
            
        }
        
    }
    
    // Photo gallery
    @IBAction func imagePickerBtn(_ sender: Any) {
        let picker = UIImagePickerController()
        picker.allowsEditing = true
        picker.delegate = self
        present(picker, animated: true)
        
    }
    
    // Dismiss our view
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        guard let imageData = info[.editedImage] as? UIImage else {return}
        imageView.image = imageData

        dismiss(animated: true)
        
    }
    
    // Task a selfie
    @IBAction func camerPictureBtn(_ sender: Any) {
        
        let photoTaker = UIImagePickerController()
        photoTaker.sourceType = .camera
        photoTaker.allowsEditing = true
        photoTaker.delegate = self
        present(photoTaker, animated: true)
    }
    
    // dismiss the picker
    func imagePickerControllerDidCancel(_ photoTaker: UIImagePickerController) {
        photoTaker.dismiss(animated: true, completion: nil)
    }
    

    /* After setting up Cloud Firestore: 1. Check the fields and validate that the data is correct.
       If everthing is correct, this method returns nil. Otherwise
       it returnes the error message as a string.
    */
    func validateFields() -> String? {
        
        // 2. Check that all fields are filled in
        if firstNameTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" ||
            lastNameTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" ||
            emailTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" ||
            passwordTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" {
            
            return "Please fill in all fields."
        }
        
        // 3. Check if the password is secure
        
        let cleanedPassword = passwordTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
        
        if Utilities.isPasswordValid(cleanedPassword) == false {
            // 4.Password isn't secure enough
            return "Please make sure your password is at least 8 characters, contains a  special character and a number."
            
        }
        
        return nil
    }

    @IBAction func signUpTapped(_ sender: Any) {

                // Make sure the selected image property isn't nil
                guard imageView.image != nil else {
                    return
                }
                
                // Create storage reference
                //guard let uid = Auth.auth().currentUser?.uid else { return }
                let storageRef = Storage.storage().reference().child("user/")
                
                //Turn image into data–
                let imageData = imageView.image!.jpegData(compressionQuality: 0.8)
                
                // Check that we are able to convert it to data
                guard imageData != nil else {
                    return
                }
                
                // Specify file path and name
                //let path = "images/profile\(UUID().uuidString).jpg"
                guard let uid = Auth.auth().currentUser?.uid else { return }
                let path = "/\(uid)"
            
                let fileRef = storageRef.child(path)
                // Upload that data
                _ = fileRef.putData(imageData!, metadata: nil) { metadata, error in
                    
                    // Check for error
                    if error == nil && metadata != nil {
                        
                        // Save a reference to that file in Firestore DB
                        let db = Firestore.firestore()
                        guard let uid = Auth.auth().currentUser?.uid else { return }
                        db.collection("users/\(uid))").document().setData(["url": path], completion: { error in
                            
                            // If there were no errors, display the new image
                            if error == nil {
                                
                                DispatchQueue.main.async {
                                    // Add the uploded image to the list of images for display
                                    self.retrieveImages.append(self.imageView.image!)
                                }
                            }
                        })
                    }
                }
    
      
        // 5. Validate the fields
        let error = validateFields()
        
        if error != nil {
            
            // 6. There's something wrong with the fields, show error message
            showError(message: error!)
            
        } else {
            
         // 8.1 Creare cleaned version of the data
            let firstName = firstNameTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
            let lastName = lastNameTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
            let email = emailTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
            let password = passwordTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
        
        // 8.2 Create the user
            Auth.auth().createUser(withEmail: email, password: password) { result, err in
                
                // 9. Check for errors
                if err != nil {
                    // 10. There was an error creating the user
                    self.showError(message: "Error creating user")
                }
                else {
                    
                    // 11. User created successfully, now store the fist name and last name
                    let db = Firestore.firestore()
                    
                    db.collection("users").addDocument(data: ["firstName": firstName, "lastName": lastName, "uid": result!.user.uid]) { (error) in
                        
                        if error != nil {
                            
                            // 12. Show error message
                            self.showError(message: "Error saving user data")
                        }
                        
                    }
                    
                    // 13. Transition to the home screen
                    self.transitionToHome()
                }
                
            }

        }
    }

    // 7. There's something wrong with the fields, show error message
    func showError(message:String) {
        
        errorLabel.text = message
        errorLabel.alpha = 1
    }
    
    func transitionToHome() {
        
        let profileVC = UIStoryboard(name: "Main", bundle: nil)
        let controller = profileVC.instantiateViewController(identifier: "tasks") as! TasksViewController

        controller.modalPresentationStyle = .fullScreen
        //present(controller , animated: true)
        
        self.view.window?.rootViewController = UINavigationController(rootViewController: controller)
        self.view.window?.makeKeyAndVisible()
        
    }
    
}


I’ve drilled it down to this being the culprit:

When I put the uid that’s generated it works fine, but when I use (uid) it does’t work. This seem like it should absolutely work, but it’s not. What’s the deal?

I’m guessing that a lot of the code in the SignUpViewController is based on the CustomLoginDemo featured in this video

Is that correct?

Can you post a screenshot of your storyboard SignUpViewController so that I can mirror the layout in my test Project.

Yes. @Chris_Ching tutorials is actually my first experience with Swift. Here is a screenshot:

@coder3000

After thinking about this for some time and considering the order in which the Firebase operations need to be done, I have come up with a solution that works.

The key to this is the order in which you do things.

In order to have a currentUser.uid you have to have a user actually logged in.

To do that in the SignUpViewController you have to “create” the user and when that has been successful you then have to “save” the profile image to storage and when that has been successful you can “save” some user data in the Firestore database.

As you can see there are activities that are dependant on each other so given that they are all performed as background tasks I have separated them into their own functions.

signUpTapped() has been modified and code within that separated out into their own specific tasks with calls to those tasks.

Replace your signUpTapped with the following version:

    @IBAction func signUpTapped(_ sender: Any) {

        errorLabel.alpha = 0
        errorLabel.text = ""

        // Make sure the selected image property isn't nil
        guard let image = imageView.image else {
            showError(message: "You must select a profile image.")
            return
        }
        //  Create the user so we have a current user for the uploadProfileImage process
        createUser { success in
            if success {
                //  Upload the image to Storage and return a url reference to the image.
                self.uploadProfileImage(image: image) { url in
                    if let profileUrl = url {
                        self.saveUserData(imageUrl: profileUrl)
                    }
                }
            }
        }
    }

Add the following 3 functions:

  • Create User

Note that this has a completion handler which returns a Boolean to indicate true is the user creation was successful and false if unsuccessful.

    func createUser(completion: @escaping (Bool) -> Void) {
        // 5. Validate the fields
        let error = validateFields()

        if error != nil {

            // 6. There's something wrong with the fields, show error message
            showError(message: error!)
            completion(false)

        } else {
            // Clean the email and password fields
            let email = emailTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
            let password = passwordTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)

            // Create the user
            Auth.auth().createUser(withEmail: email, password: password) { result, error in

                // 9. Check for errors
                if error != nil {
                    // Unwrap error because it is Optional.
                    if let error = error {
                        self.showError(message: error.localizedDescription)
                    }
                    completion(false)
                } else {
                    completion(true)
                }
            }
        }
    }
  • Upload Profile Image

This also has a completion handler and returns an Optional URL since the image may or may not have been saved.

    func uploadProfileImage(image: UIImage, completion: @escaping (URL?) -> Void) {

        // Extract user id to use in file path
        guard let uid = Auth.auth().currentUser?.uid else { return }

        // Create storage reference
        let storageRef = Storage.storage().reference()

        // Turn our image into data
        let imageData = image.jpegData(compressionQuality: 0.8)

        // Check that we were able to convert it to data
        guard imageData != nil else { return }

        // Specify the file path and name
        let path = "profileimages/\(uid).jpg"
        let fileRef = storageRef.child(path)

        fileRef.putData(imageData!, metadata: nil) { metadata, error in

            //  Check for errors
            if error == nil && metadata != nil {
                //  Get the url for the image in storage
                fileRef.downloadURL { url, error in
                    //  Check for errors
                    if url != nil && error == nil {
                        // return the url
                        completion(url)
                    }
                }
            } else {
                // Unwrap error because it is Optional.
                if let error = error {
                    self.showError(message: error.localizedDescription)
                }
            }
        }
    }
  • Save User Data:
    func saveUserData(imageUrl: URL) {

        // 5. Validate the fields
        let error = validateFields()

        if error != nil {

            // 6. There's something wrong with the fields, show error message
            showError(message: error!)

        } else {

            // 8.1 Create cleaned version of the data
            let firstName = firstNameTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
            let lastName = lastNameTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)


            // 11. User created successfully, now store the fist name and last name
            let db = Firestore.firestore()

            db.collection("users").addDocument(data:
                                                ["firstName": firstName,
                                                 "lastName": lastName,
                                                 "uid": Auth.auth().currentUser?.uid ?? "",
                                                 "profileimageurl": imageUrl.absoluteString]
            ) { (error) in

                if error != nil {

                    // 12. Show error message
                    // Unwrap error because it is Optional.
                    if let error = error {
                        self.showError(message: error.localizedDescription)
                    }
                } else {
                    self.showError(message: "Successfully saved user data.")

                    // 13. Transition to the home screen
                    self.transitionToHome()
                }
            }
        }
    }

Anyway I hope all this makes sense to you.