Cannot assign to value: 'completion' is a 'let' constant in swift - swift

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

You need to write completion like this completion(<#value here#>) not to assign it
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)")
}
}
}

You don't need to assign a value to completion parameter, you need to invoke it like following -
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)")
}
}

Related

How to wait for firebase storage to upload image before using it?

I am trying to create a post but I have to wait for the image to upload to firebase storage before I can actually create the post. This just creates a post with an empty imageURL:
private func createFeedPost(feedPost: FeedPost) {
Task {
await persistImageToStorage()
}
FirebaseManager.shared.firestore.collection("feedposts").document(feedPost.id)
.setData(["commentsCount": feedPost.commentsCount, "description": feedPost.description,
"feedImageUrl": feedPost.feedImageUrl, "id": feedPost.id, "likesCount": feedPost.likesCount,
"locked": feedPost.locked,
"price": feedPost.price, "timestamp": feedPost.timestamp])
}
private func persistImageToStorage() async {
let ref = FirebaseManager.shared.storage.reference(withPath: imageId)
guard let imageData = self.image?.jpegData(compressionQuality: 0.5)
else { return }
ref.putData(imageData, metadata: nil) { metadata, err in
if let err = err {
print(err)
return
}
ref.downloadURL { url, err in
if let err = err {
print(err)
return
}
guard let url = url else { return }
self.imageUrl = url.absoluteString // here is where I set the imageURL
}
}
}
Is there a way to get something back from firebase storage once the image was uploaded?
EDIT:
I tried using a completion but this also didn't set the imageURL
private func persistImageToStorage(complete:()->()) {
let ref = FirebaseManager.shared.storage.reference(withPath: imageId)
guard let imageData = self.image?.jpegData(compressionQuality: 0.5)
else { return }
ref.putData(imageData, metadata: nil) { metadata, err in
if let err = err {
print(err)
return
}
ref.downloadURL { url, err in
if let err = err {
print(err)
return
}
guard let url = url else { return }
self.imageUrl = url.absoluteString
}
}
complete()
}
private func createFeedPost(feedPost: FeedPost) {
persistImageToStorage(complete: { () -> () in
FirebaseManager.shared.firestore.collection("feedposts").document(feedPost.id).setData(["commentsCount": feedPost.commentsCount, "description": feedPost.description, "feedImageUrl": feedPost.feedImageUrl, "id": feedPost.id, "likesCount": feedPost.likesCount, "locked": feedPost.locked, "price": feedPost.price, "timestamp": feedPost.timestamp])
})
}
For any time-consumption or long-waiting, I recommend using the async logic like what you were thinking about.
Here is your typealias to make your type definition more meaningful.
/// (File URL, Error Message/Code
typealias FireBaseUploadResponseHandler = (String?, String?) -> Void
So your code will be:
private func persistImageToStorage(image: UIImage?, done: #escaping FireBaseUploadResponse) {
guard let imageData = image?.jpegData(compressionQuality: 0.5)
else { return done(nil, "IMG_TO_DATA_FAILED") }
let imageId = UUID().uuidString
let ref = FirebaseManager.shared.storage.reference(withPath: imageId)
ref.putData(imageData, metadata: nil) { metadata, err in
if let err = err {
print(err)
return done(nil, err.localizedDescription)
}
ref.downloadURL { url, err in
guard let url = url else { return done(nil, err.localizedDescription) }
done(url.absoluteString, nil) // Pass your downloaded URL here to closure `done`
}
}
}
Usage:
private func createFeedPost(feedPost: FeedPost, done: escaping (Bool) -> Void) {
self.persistImageToStorage(image: self.image) { url, error in
guard let _url = url else { return done(false) }
// now _url is available to use, it is public url of your uploaded image
FirebaseManager.shared.firestore.collection("feedposts")
.document(feedPost.id)
.setData(["commentsCount": feedPost.commentsCount, "description": feedPost.description,
"feedImageUrl": feedPost.feedImageUrl, "id": feedPost.id, "likesCount": feedPost.likesCount,
"locked": feedPost.locked,
"price": feedPost.price, "timestamp": feedPost.timestamp])
done(true)
}
}
Then
createFeedPost(feedPost: [your post here]) { [weak self] success in
guard let _self = self else { return }
if success {
_self.[your function to notify user that post is created]()
} else {
_self.[your function to notify user that it failed to create the post]()
}
}
For this bunch of codes:
["commentsCount": feedPost.commentsCount, "description": feedPost.description,
"feedImageUrl": feedPost.feedImageUrl, "id": feedPost.id, "likesCount": feedPost.likesCount,
"locked": feedPost.locked,
"price": feedPost.price, "timestamp": feedPost.timestamp]
I suggest you use any JSON encoder/decoder/parser like ObjectMapper or Codable (built-in).
To simplify your logic and easy to maintain it.

return value from async Firestore query swift

I am trying to query my Firestore database to see if a desired username is taken. The query works, however I need to return a value if it is empty or not to see if the username already exists. I am trying to use a completion handler but it doesn't seem to work:
func checkUserTaken(cleanUsername: String ,completion:#escaping(String) -> (Void)){
let db = Firestore.firestore()
var userTaken: String = ""
let docRef = db.collection("users").whereField("username", isEqualTo: cleanUsername)
docRef.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
if(querySnapshot!.documents.isEmpty){
print("user is available")
userTaken = "user is available"
}
else{
print("user is taken")
userTaken = "user is taken"
}
}
completion(userTaken)
}
This task is a good way to learn some important and helpful things about Swift, such as naming conventions, deciding what to return from functions (you chose a string, I opted for a boolean), accounting for errors, etc.
func checkUsername(_ username: String, completion: #escaping (_ taken: Bool?) -> Void) {
Firestore.firestore().collection("users").whereField("username", isEqualTo: username).getDocuments() { (snapshot, err) in
if let snapshot = snapshot {
if snapshot.documents.isEmpty {
completion(false)
} else {
completion(true)
}
} else {
if let err = err {
print(err)
}
completion(nil)
}
}
}
Usage
checkUsername("drake") { (taken) in
guard let taken = taken else {
// handle error, maybe retry?
return
}
if taken {
// prompt user username is taken
} else {
// username not taken, proceed
}
}
In the signature of the function, I labeled the boolean in the completion closure (taken), which the Swift compiler does not require you do but I think can be very helpful.
By the way, this function can return anything, even a Result object which is a neat way to return an object or an Error in a single object. But I think returning a boolean is straightforward enough here. I made the boolean an optional so the function can return three possible states (true, false, or nil) to give you a way to handle errors.
String return
func checkUsername(_ username: String, completion: #escaping (_ name: String?) -> Void) {
Firestore.firestore().collection("users").whereField("username", isEqualTo: username).getDocuments() { (snapshot, err) in
if let snapshot = snapshot {
if snapshot.documents.isEmpty {
completion(username) // return the username if it's available
} else {
completion("") // return an empty string if taken
}
} else {
if let err = err {
print(err)
}
completion(nil) // return nil if error
}
}
}
checkUsername("drake") { (name) in
guard let name = name else {
// handle error
return
}
if name.isEmpty {
// username taken
} else {
print(name) // username not taken
}
}

Upload Profile Pic. Cannot assign photo picked from library to profilepic in users node

func uploadProfileImage(_ image:UIImage, completion: #escaping ((_ url:URL?)->())) {
let storageRef = Storage.storage().reference().child("profileImages").child("\(NSUUID().uuidString).jpg")
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 {
storageRef.downloadURL (completion: {(url, error) in
if error != nil {
if let downloadurl = url?.absoluteString {
if (self.profilePicLink == "") {
self.profilePicLink = downloadurl
Database.database().reference().child("users").child(self.uid).updateChildValues(["profilePicLink":downloadurl])
}
}
} else {
completion(nil)
}
}
)
}
}
}
I am trying to assign upload photo from my library and then assign it to profilePicLink. When I choose pic from my library, it appears in the UIImage ImageView frame, however. When I use these func and action to update avatar it went error
#IBAction func updateAvatar(_ sender: Any) {
uploadPhoto()
}
func uploadPhoto(){
selectedUser?.uploadProfileImage(imageView.image!){
url in print (URL.self)
}
}
After I check in Firestore, there is no pic stored and my profilePicLink has no value.
Please be advised on this
You are checking for error != nil, metadata != nil together. It's contradicting each other hence that condition is never executed. Change it to:
if error == nil, metadata != nil {
...
}

Trouble calling async function with #escaping handler

I'm currently working with Firebase and Firestore and I'm working with their distributed counter system and have created a function with a #escaping handler that should return a value I can assign and then display. here's the code:
func getCount(ref: DocumentReference, handler: #escaping(_ querySnapshot: QuerySnapshot,_ err: Error) -> Void) {
ref.collection("shards").getDocuments() { (querySnapshot, err) in
var totalCount = 0
if err != nil {
print("error getting total count")
} else {
for document in querySnapshot!.documents {
let count = document.data()["count"] as! Int
totalCount += count
}
}
}
}
I'm having trouble calling the function and there isn't too much information that gives me a solid grasp about this. Can someone point me in the right direction?
I call it here:
getCount(ref: Featuredlikes {
ref.collection("shards").getDocuments() { (querySnapshot, err) in
var totalCount = 0
if err != nil {
print("error getting total count")
} else {
for document in querySnapshot!.documents {
let count = document.data()["count"] as! Int
totalCount += count
}
}
}
}
But evidently it isn't right I'm getting since I'm getting unrelated errors in the swiftui part of my code.
Basically your code cannot work because it's impossible to return (strictly spoken the closure does not return anything) a non-optional snapshot and non-optional error simultaneously. Declare both parameters as optional. By the way an underscore character and parameter label in the closure declaration is Swift 2 legacy code.
You have to call handler either with nil snapshot and the error instance or vice versa.
func getCount(ref: DocumentReference, handler: #escaping(QuerySnapshot?, Error?) -> Void) {
ref.collection("shards").getDocuments() { (querySnapshot, err) in
if let error = err {
handler(nil, error)
} else {
handler(querySnapshot, nil)
}
}
}
Or with the modern Result API
func getCount(ref: DocumentReference, handler: #escaping(Result<QuerySnapshot, Error>) -> Void) {
ref.collection("shards").getDocuments() { (querySnapshot, err) in
if let error = err {
handler(.failure(error))
} else {
handler(.success(querySnapshot))
}
}
}

Firebase downloadUrl() is deprecated [duplicate]

Storage.storage().reference().child(ImageUid).putData(ImageData, metadata: metadata) { (metadata, error) in
if error != nil {
print("Couldn't Upload Image")
} else {
print("Uploaded")
let downloadURl = metadata?.downloadURL()?.absoluteString
if let url = downloadURl {
self.SetUpUser(Image: url)
}
}
}
}
}
Error:
'downloadURL()' is deprecated: Use
StorageReference.downloadURLWithCompletion() to obtain a current
download URL.
How do I fix this?
The error says that you need to use StorageReference.downloadURLWithCompletion() well you need to use it:
let storageItem = Storage.storage().reference().child(ImageUid)
storageItem.putData(ImageData, metadata: metadata) { (metadata, error) in
if error != nil {
print("Couldn't Upload Image")
} else {
print("Uploaded")
storageItem.downloadURL(completion: { (url, error) in
if error != nil {
print(error!)
return
}
if url != nil {
self.SetUpUser(Image: url!.absoluteString)
}
}
}
}