Getting error "Variable used within its own initial value" - swift

I am writing a new bit of code that allows a user to upload an image to a post that will be saved in Firebase database and storage. Then it will be shown in a UITableView on a separate Viewcontroller. On the part of code where I want to save the photo url to my database I am getting an error "Variable used within its own initial value". I have looked at other threads covering this issue and when I follow their advice I get a new set of errors. Here is what my code looks like and where I am getting the error. If there are any suggestions that would be great, thank you!
Here is code:
func postWithImageAndText() {
var imagesArray = [AnyObject]()
let postLength = textView.text.count
let numImages = imagesArray.count
let postRef = Database.database().reference().child("posts").childByAutoId()
let storageRef = Storage.storage().reference()
let uid = Auth.auth().currentUser
let pictureStorageRef = storageRef.child("users/profile/\(uid)/media")
let lowResImageData = (imagesArray[0] as! UIImage).jpegData(compressionQuality: 0.50)
if(postLength>0 && numImages>0)
{
let uploadTask = pictureStorageRef.putData(lowResImageData!,metadata: nil)
{metadata,error in
if (error == nil) {
let downloadUrl = storageRef.downloadURL { (url, error) in
guard let userProfile = UserService.currentUserProfile else { return }
let childUpdates = [
"author": [
"uid": userProfile.uid,
"username": userProfile.fullname,
"photoURL": userProfile.photoURL.absoluteString
],
"text": self.textView.text!,
"picture": downloadUrl!.absoluteString,
"timestamp": ["sv.":"timestamp"]
] as [String:Any]
}
}
}
}
}
`
Error is happening on this part: "picture": downloadUrl!.absoluteString,

As i can see yo got an "Variable used within its own initial value" error, that means you try to access initial value of variable within this variable, its not a good behavior.
I also see you dont use any data from your completion block, so try to use nil instead of your completion block and then try to access your downloadURL variable

Your updated code:
func postWithImageAndText() {
var imagesArray = [AnyObject]()
let postLength = textView.text.count
let numImages = imagesArray.count
let postRef = Database.database().reference().child("posts").childByAutoId()
let storageRef = Storage.storage().reference()
let uid = Auth.auth().currentUser
let pictureStorageRef = storageRef.child("users/profile/\(uid)/media")
if let lowResImageData = (imagesArray[0] as! UIImage).jpegData(compressionQuality: 0.50) {
if(postLength>0 && numImages>0)
{
pictureStorageRef.putData(lowResImageData, metadata: metaData) { (metadata, error) in
if error != nil {
print("Failed to upload image:", error as Any)
}
pictureStorageRef.downloadURL(completion: { (url, error) in
if error != nil {
print(error!)
}
if let imageUrl = url?.absoluteString {
guard let userProfile = UserService.currentUserProfile else { return }
let childUpdates = [
"author": [
"uid": userProfile.uid,
"username": userProfile.fullname,
"photoURL": userProfile.photoURL.absoluteString
],
"text": self.textView.text!,
"picture": imageUrl,
"timestamp": ["sv.":"timestamp"]
] as [String:Any]
}
})
}
}
}
}

Related

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

Struggling with basic Swift logic (Working with constants, variables and loops)

I want to use 2 Strings that I get with those two "loops" from firebase and use them in another "loop" to upload them with a bunch of other Information.
My problem is, that I somehow can't get the values of fullname and pfp that I downloaded, into the upload to firebase.
Any ideas on how to solve this issue?
func sendToFire(){
let combined = "\(userID)" + "\(number)"
let docRef = db.collection("posts").document(combined)
let description = self.textPost.text
let nameRef = db.collection("users").document(userID)
var fullname = ""
var pfp = ""
if fireImage == nil {
nameRef.getDocument { (document, error) in
if let document = document{
fullname = document.get("fullname") as! String
}else{
print("Coulnt get fullname")
}
}
nameRef.getDocument { (document, error) in
if let document = document{
pfp = document.get("profileimage") as! String
}else{
print("Couldn't get profileimage")
}
}
docRef.getDocument { (document, error) in
if let document = document, document.exists {
print("Post ID already taken")
} else {
print("Post Document gets created")
self.db.collection("posts").document(combined).setData([
"description": description!,
"likes": self.likes,
"postType": 0,
"profileImage": pfp,
"time": self.date,
"uid": self.userID,
"username": fullname
]) { err in
if let err = err {
print("Error writing document: \(err)")
} else {
print("Post Document successfully written!")
}
}
}
}
}
}
Add document.exists in the if let
nameRef.getDocument { (document, error) in
if let document = document, document.exists{
fullname = document.get("fullname") as! String
}else{
print("Coulnt get fullname")
}
}
nameRef.getDocument { (document, error) in
if let document = document, document.exists{
pfp = document.get("profileimage") as! String
}else{
print("Couldn't get profileimage")
}
}
Check the actual key names in the response fullname and profileimage.

Access variable outside of function Swift

How can I get the value from firstName from the inside:
func saveImage(name: String, postURL:URL, completion: #escaping ((_ url: URL?) -> ())){
//Get sspecific document from current user
let docRef = Firestore.firestore().collection("users").whereField("uid", isEqualTo: Auth.auth().currentUser?.uid ?? "")
var firstName = ""
// Get data
docRef.getDocuments { (querySnapshot, err) in
var firstName = ""
if let err = err {
print("ERROR: ")
print(err.localizedDescription)
return
} else if querySnapshot!.documents.count != 1 {
print("More than one documents or none")
} else {
let document = querySnapshot!.documents.first
let dataDescription = document?.data()
firstName = dataDescription?["firstname"] as! String
}
}
// This uploads the data
let dict = ["title": postDescriptionTitle.text!,
"description": postDescription.text!,
"Address": addressField.text!,
"Zipcode": zipcodeField.text!,
"timestamp": [".sv":"timestamp"],
"Author":firstName,
"postUrl": postURL.absoluteString]
as [String: Any]
self.ref.child("post").childByAutoId().setValue(dict)
}
It looks like it's out of scope, how can I store it or access it without storing it in another variable?
As you can see, I'm trying to upload the variable firstName to the database. So in this part:
"Author":firstName,
I should be getting the value so I can give it to Author
Just move the "upload data" part inside the completion block like this:
func saveImage(name: String, postURL:URL, completion: #escaping ((_ url: URL?) -> ())) {
//Get sspecific document from current user
let docRef = Firestore.firestore().collection("users").whereField("uid", isEqualTo: Auth.auth().currentUser?.uid ?? "")
// Get data
docRef.getDocuments { (querySnapshot, err) in
if let err = err {
print("ERROR: ")
print(err.localizedDescription)
return
} else if querySnapshot!.documents.count != 1 {
print("More than one documents or none")
} else {
let document = querySnapshot!.documents.first
let dataDescription = document?.data()
let firstName = dataDescription?["firstname"] as! String
// This uploads the data
let dict = ["title": self.postDescriptionTitle.text!,
"description": self.postDescription.text!,
"Address": self.addressField.text!,
"Zipcode": self.zipcodeField.text!,
"timestamp": [".sv":"timestamp"],
"Author": firstName,
"postUrl": postURL.absoluteString] as [String: Any]
self.ref.child("post").childByAutoId().setValue(dict)
}
}
}
Also for what are you using the completion argument in your saveImage function?

InvalidFirebaseData during Sign Up process

Currently, I am using a combination of Firebase authentication, database, and storage for my sign up process.
#objc func handleSignUp() {
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 else { return }
Auth.auth().createUser(withEmail: email, password: password) { (user, error) in
//handle error
if let error = error {
print("Failed to create 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 firbase storage
let filename = NSUUID().uuidString
let storage = Storage.storage()
let storageReference = storage.reference()
let imagesReference = storageReference.child("profile_images")
let uploadReference = imagesReference.child(filename)
uploadReference.putData(uploadData, metadata: nil) { (metadata, error) in
//handle error
if let error = error {
print("Failed to upload image to Firebase Storage with error", error.localizedDescription)
}
//profile image url
let profileImageURL = uploadReference.downloadURL
//user id
let uid = user?.user.uid
let dictionaryValues = ["name": fullName, "username": username, "profileImageURL": profileImageURL]
let values = [uid: dictionaryValues]
//save user info to database
Database.database().reference().child("users").updateChildValues(values) { (error, ref) in
print("Successfully created user and saved information to database")
}
}
}
This should be the code, which should be working fine.
However, xCode is giving me this error:
Heterogeneous collection literal could only be inferred to '[String : Any]'; add explicit type annotation if this is intentional
and is advising me to change
let dictionaryValues = ["name": fullName, "username": username, "profileImageURL": profileImageURL]
to
let dictionaryValues = ["name": fullName, "username": username, "profileImageURL": profileImageURL] as [String : Any]
As a result, I can compile and run the project.
However, when I press the sign-up button and therefore run the given function, the console is showing this error:
2019-09-30 19:15:13.049986+0200 5iveli0ns[965:28839] [] nw_connection_receive_internal_block_invoke [C2] Receive reply failed with error "Operation canceled"
and in the end I receive a Runtime Exception:
*** Terminating app due to uncaught exception 'InvalidFirebaseData', reason: '(updateChildValues:withCompletionBlock:) Cannot store object of type __SwiftValue at profileImageURL. Can only store objects of type NSNumber, NSString, NSDictionary, and NSArray.'
Is this an xCode 11 / Firebase 5.x.x related issue?
Generally speaking, Dictionaries infer their structure from the data they contain. So for example
let dict = [
"name": "Leroy",
"fav_food": "Pizza"
]
is a [String: String] Dictionary
Likewise
let dict = [
"name": "Leroy"
"age": 25
]
is a [String: Int] Dictionary.
What you have is
let dict = [
"name": "Leroy",
"username": "some username"
"profileImageUrl": URL //oops, not a string, its a URL!
]
When Dictionary values are consistent, they can be defined [String: String]. However, when the value types will vary, they are [String: Any]
The other issue is that Firebase doesn't support the URL type, so even if you used a Dictionary of [String: Any], Firebase will complain - as shown by the error.
The fix is easy - just use the path, which is a string of the url
if let url = uploadReference.downloadURL {
let path = url
let dict = [
"name": "Leroy",
"username": "some username",
"path": path //it's a string
]
//write dict to Firebase
}
COnvert
let profileImageURL = uploadReference.downloadURL
to String then feed it into the dictionaryValues array.
I hope that helps

Swift 3 - How to download Profile Image from FireBase Storage

I need help on retrieving Image from firebase storage, i learned how to save it but cant download it to current user profile.
here is my code so far:
FIRAuth.auth()?.createUserWithEmail(email!, password: password!, completion: { (authData, error) in
if error == nil {
if (password == "" || name == "" || email == "") {
self.showAlert("error", message: " Please Fill In The Blank")
}
if (password != confirm_password) {
self.showAlert("ERROR", message: "Password Don't Match")
}
}
else {
self.showAlert("ERROR", message: "Please Try Again")
}
let filePath = "\(FIRAuth.auth()!.currentUser!.uid)/\("userPhoto")"
var data = NSData()
let metaData = FIRStorageMetadata()
//let imageName = NSUUID().UUIDString
let storageRef = FIRStorage.storage().referenceForURL("gs://storageURL")
storageRef.child(filePath).putData(data, metadata: metaData){(metaData,error) in
if let error = error {
print(error.localizedDescription)
return
}
if let ProfileImageUrl = metaData?.downloadURL()?.absoluteString {
let values : [String : AnyObject] = ["name": name!, "email": email!, "profileImageUrl": ProfileImageUrl]
self.userPost(values)
}
}
})
}
//save data in Database
func userPost(values: [String: AnyObject]) {
let ref = FIRDatabase.database().referenceFromURL("https://databaseURL.firebaseio.com/")
ref.child("users").childByAutoId().setValue(values)
}
so i got that so far but cant figure out how to download it to user profile.
here is my code for user ProfileVC:
#IBOutlet weak var ProfileImg: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.retriveData()
ProfileImg.image = UIImage(named: "ic_account_circle.png")
imagePicker.delegate = self
}
func DownloadProfilePhoto() {
let storageRef = FIRStorage.storage().referenceForURL("gs://StorageURL.com")
let filePath = "\(FIRAuth.auth()!.currentUser!.uid)/\("userPhoto")"
}
Please Help.....
You need to download the image data, and then create an image. You should try this:
let storage = FIRStorage.storage()
var reference: FIRStorageReference!
reference = self.storage.referenceForURL("gs://appname.appspot.com/filePath")
reference.downloadURLWithCompletion { (url, error) in
let data = NSData(contentsOfURL: url!)
let image = UIImage(data: data!)
ProfileImg.image = image
}
You should check the Firebase documentation https://firebase.google.com/docs/storage/ios/download-files
For people looking for swift 4
let storage = Storage.storage()
var reference: StorageReference!
reference = storage.reference(forURL: "gs://appname.appspot.com/filePath")
reference.downloadURL { (url, error) in
let data = NSData(contentsOf: url!)
let image = UIImage(data: data! as Data)
cell.imgOutlet.image = image
}