For loop not accounting for all items? - swift

I have a multi-selection imagepicker with the intention of allowing the user to select multiple assets, then upload each asset to the database. In the completion handler, I take all the selected assets and pass them to a custom function: uploadImageAssets(assets: [PHAsset], projectRef: DocumentReference), where the upload begins.
In the function, I'm using a for-loop to upload each asset individually. While the assets are being uploaded correctly, not all assets are being uploaded. Lets say I've selected 5 assets... Only 4 will show up in the database, and they'll all be the same image, repeated. Any idea as to why this is happening? Here is my code below:
Image Picker Selection:
#IBAction func uploadProjectTapped(_ sender: Any) {
let imagePicker = ImagePickerController()
imagePicker.settings.selection.max = 10
imagePicker.settings.theme.selectionStyle = .numbered
imagePicker.settings.fetch.assets.supportedMediaTypes = [.image, .video]
imagePicker.settings.selection.unselectOnReachingMax = false
let start = Date()
self.presentImagePicker(imagePicker, select: { (asset) in
print("Selected: \(asset)")
}, deselect: { (asset) in
print("Deselected: \(asset)")
}, cancel: { (assets) in
print("Canceled with selections: \(assets)")
}, finish: { (assets) in
print("Finished with selections: \(assets)")
self.getAssetThumbnail(assets: assets)
}, completion: {
let finish = Date()
print(finish.timeIntervalSince(start))
})
}
And, the function to add them to Firestore:
func uploadImageAsset(assets: [PHAsset], projectRef: DocumentReference) {
let userID = Auth.auth().currentUser?.uid
let db = Firestore.firestore()
let manager = PHImageManager.default()
let option = PHImageRequestOptions()
option.isSynchronous = false
option.isNetworkAccessAllowed = true
option.resizeMode = .exact
option.version = .original
option.deliveryMode = .highQualityFormat
let uniqueImageID = NSUUID().uuidString
let storageRef = Storage.storage().reference().child("project-images").child("\(uniqueImageID).jpeg")
for asset in assets {
let imageSize = CGSize(width: asset.pixelWidth, height: asset.pixelHeight)
manager.requestImage(for: asset, targetSize: imageSize, contentMode: .aspectFill, options: option) { (image, info) in
let uploadData = image?.jpegData(compressionQuality: 0.6)
storageRef.putData(uploadData!, metadata: nil, completion: {
(metadata, error) in
if error != nil {
return
} else {
storageRef.getMetadata(completion: { (metadata, err) in
if let error = err {
print(error)
} else {
storageRef.downloadURL(completion: { (url, err) in
if let error = err {
print(error)
} else {
self.imageAssetURLs.append((url?.absoluteString)!)
guard let url = url?.absoluteString else { return }
projectRef.updateData(["images": FieldValue.arrayUnion([url])], completion: { (err) in
if err != nil {
print(err)
} else {
self.dismiss(animated: true, completion: nil)
}
})
}
})
}
})
}
})
}
}
}
I have a strong feeling that the error lies within this line:
self.imageAssetURLs.append((url?.absoluteString)!)
guard let url = url?.absoluteString else { return }

in func uploadImageAsset(...) the
let uniqueImageID = NSUUID().uuidString
and
let storageRef = Storage.storage().reference().child("project-images").child("\(uniqueImageID).jpeg")
should be inside the loop just before
storageRef.putData(..)

Related

Completion not working with Firebase Database

The user can upload a profile picture and some information about himself in my app.
I want to write the url of the uploaded picture in firebase realtime database but it takes the placeholder text "testentry" and not the real url. Why does my completion not work here?
var imagePicker: UIImagePickerController!
var urltoPicture = "testentry"
#IBAction func updateProfile(_ sender: UIButton) {
uploadPic(arg: true, completion: { (success) -> Void in
if success {
linkUbertragen()
} else {
}
})
func uploadPic(arg: Bool, completion: #escaping (Bool) -> ()) {
guard let imageSelected = self.image else {
//print("ok")
return
}
guard let imageData = imageSelected.jpegData(compressionQuality: 0.1) else {
return
}
let storageRef = Storage.storage().reference(forURL: "gs://h......com")
let storageProfileRef = storageRef.child("profilePictures").child(Auth.auth().currentUser!.uid)
let metadata = StorageMetadata()
metadata.contentType = "image/jpg"
storageProfileRef.putData(imageData, metadata: metadata, completion: {
(storageMetadata, error) in
if error != nil {
//print(error?.localizedDescription)
return
}
storageProfileRef.downloadURL(completion: { (url, error) in
if let metaImageURL = url?.absoluteString {
print(metaImageURL)
self.urltoPicture = metaImageURL
}
})
})
completion(arg)
}
func linkUbertragen(){
ref = Database.database().reference()
let userID = Auth.auth().currentUser!.uid
ref.child("user/\(userID)").updateChildValues(["profileText": profileText.text!])
print(urltoPicture)
ref.child("user/\(userID)").updateChildValues(["picture": urltoPicture])
}
self.navigationController?.popViewController(animated: true)
}
This is a very common mistake. You have to call completion inside the (final) closure.
And it is good practice to call completion(false) always in case of an error – even better to return and handle all errors
func uploadPic(arg: Bool, completion: #escaping (Bool) -> ()) {
guard let imageSelected = self.image else {
//print("ok")
completion(false); return
}
guard let imageData = imageSelected.jpegData(compressionQuality: 0.1) else {
completion(false); return
}
let storageRef = Storage.storage().reference(forURL: "gs://h......com")
let storageProfileRef = storageRef.child("profilePictures").child(Auth.auth().currentUser!.uid)
let metadata = StorageMetadata()
metadata.contentType = "image/jpg"
storageProfileRef.putData(imageData, metadata: metadata, completion: {
(storageMetadata, error) in
if error != nil {
//print(error?.localizedDescription)
completion(false); return
}
storageProfileRef.downloadURL(completion: { (url, error) in
if let metaImageURL = url?.absoluteString {
print(metaImageURL)
self.urltoPicture = metaImageURL
completion(true)
} else {
completion(false)
}
})
})
}
The arg parameter is actually not needed.

"The file "..." couldn’t be opened because there is no such file" uploading video from phpickerviewcontroller to FirebaseStorageq

I'm trying to upload a video file using phpickerviewcontroller, but I'm running into an issue uploading the URL to FirebaseStorage. Here is some code:
func uploadVideo(videoURL: URL)
{
let storage = Storage.storage()
let storageRef = storage.reference()
let videoRef = storageRef.child("rPosts/\(uid!)/\(fileID)")
let metadata = StorageMetadata()
metadata.contentType = "video/quicktime"
var videoData: Data = Data()
do
{
videoData = try Data(contentsOf: videoURL)
}
catch
{
print(error.localizedDescription)
return
}
videoRef.putData(videoData, metadata: metadata)
{ (metaData, error) in
guard error == nil else
{
self.errorLabel.text = error!.localizedDescription
return
}
}
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult])
{
dismiss(animated: true, completion: nil)
guard let itemProvider = results.first?.itemProvider else { return }
itemProvider.loadItem(forTypeIdentifier: "com.apple.quicktime-movie", options: nil)
{ (videoFile, error) in
guard error == nil else { return }
let videoFile = videoFile as? URL
DispatchQueue.main.async
{
self.uploadVideo(videoURL: videoFile!)
print(videoFile!)
}
self.uploadedYet = true
}
}
I've tried using .putFile but it keeps on saying
Ensure file URL is not a directory, symbolic link, or invalid url.
When I use .putData it says
The file "..." couldn’t be opened because there is no such file
EDIT:
itemProvider.loadFileRepresentation(forTypeIdentifier: "com.apple.quicktime-movie")
{ (videoURL, error) in
guard error == nil else { return }
print("isbeingcalled") //does not get calleed :(
DispatchQueue.main.async
{
let storageRef = Storage.storage().reference()
let videoRef = storageRef.child("rPosts/\(self.uid!)/\(self.fileID).mov")
let metadata = StorageMetadata()
metadata.contentType = "video/quicktime"
print("run")
videoRef.putFile(from: videoURL!, metadata: metadata)
{ (metaData, error) in
guard error == nil else
{
print(videoURL!)
print(videoRef.fullPath)
self.errorLabel.text = error!.localizedDescription
print(error!.localizedDescription)
return
}
}
}
self.uploadedYet = true
}

How to load image from Firebase into users avatar

I have a problem with loading images from firebase. I have two functions. One of them collect info about user, second one load users avatar image. Unfortunately images load after function creates new user. I know it will be problem with asynchronous of Firebase but I don't know how to set up DispatchQueue to work properly. Can you help me with that?
// function that load user image in user manager class
func loadUserImage(contactUserID: String, completion: #escaping (UIImage) -> Void) {
let userID = Auth.auth().currentUser!.uid
var userImageRef = self.storage.child("\(userID)/userImage.jpg")
var image = UIImage()
if contactUserID != "" {
userImageRef = self.storage.child("\(contactUserID)/userImage.jpg")
}
userImageRef.getData(maxSize: 5 * 1024 * 1024) { (data, error) in
if let error = error {
print("Error with retrieving data: \(error.localizedDescription)")
} else {
if data?.count != 0 {
image = UIImage(data: data!)!
} else {
image = UIImage(systemName: "person.circle.fill")!
}
completion(image)
}
}
}
// function that load user in contact manager class
func loadContactList(completion: #escaping ([User]) -> Void) {
let currentUserID = Auth.auth().currentUser!.uid
db.collection("contacts")
.document(currentUserID)
.collection("userContacts")
.addSnapshotListener { (querySnapshot, error) in
var contactList = [User]()
if let error = error {
print("Error with retrieving data from DB: \(error.localizedDescription)")
} else {
if let snapshotDocuments = querySnapshot?.documents {
for document in snapshotDocuments {
let data = document.data()
let uid = data["uid"] as! String
let name = data["name"] as! String
let email = data["email"] as! String
var contact = User(email: email, name: name, userID: uid)
DispatchQueue.global().sync {
self.userService.loadUserImage(contactUserID: uid) { (image) in
contact.photoURL = image
}
}
contactList.append(contact)
contactList.sort {
$0.name < $1.name
}
completion(contactList)
}
}
}
}
}
// Function implementation in viewController
func loadContactList() {
self.contactService.loadContactList { (contactArray) in
self.contactList = contactArray
self.tableView.reloadData()
}
}
What you can do is to store the image url in the firebase database and after that create this extension:
import UIKit
let imageCache: NSCache = NSCache<AnyObject, AnyObject>()
extension UIImageView {
func loadImageUsingCacheWithUrlString(urlString: String) {
self.image = nil
if let cachedImage = imageCache.object(forKey: urlString as AnyObject) as? UIImage {
self.image = cachedImage
return
}
let url = URL(string: urlString)
if let data = try? Data(contentsOf: url!) {
DispatchQueue.main.async(execute: {
if let downloadedImage = UIImage(data: data) {
imageCache.setObject(downloadedImage, forKey: urlString as AnyObject)
self.image = downloadedImage
}
})
}
}
}
And call:
if let url = data["imgUrl"] as? String {
self.myImageView.loadImageUsingCacheWithUrlString(urlString: url)
}
For that what you need to do is to create and initialize an UIImage object. If you are working with cell classes you need to create this object in the cell.

Why do I have Problem with upload pic in swift. Problem with nil unwrap. URL

** Update**
My point is im trying to match my profilePicLink with image I upload from my library.
Also selectedUsers is Users Type as following
var username : String = ""
var email : String = ""
var uid : String = ""
var profilePicLink : String = ""
init(username : String, email: String, uid : String, profilePicLink: String ) {
self.username = username
self.email = email
self.uid = uid
self.profilePicLink = profilePicLink
}
I am having problem when I am trying to upload photo. The action are
I pick the photo from my library
#IBAction func getPhotoButton(_ sender: Any) {
let image = UIImagePickerController()
image.delegate = self
image.sourceType = UIImagePickerController.SourceType.photoLibrary
self.present(image, animated: true, completion: nil)
}
It leads me to my photo library. After I pick my photo. I click on button "Update" with the action as following code
#IBAction func updatePhoto(_ sender: Any) {
uploadPhoto()
}
func uploadPhoto(){
selectedUser?.uploadProfileImage(imageView.image!){
url in print (URL.self)
}
}
I got the error as ** Fatal error: Unexpectedly found nil while unwrapping an Optional value: ** in the func uploadPhoto as the picture
Fatal Error
And here is the code of func in my other class (Users) for upload and get Profile Image
func getProfileImage() -> UIImage {
if let url = NSURL(string: profilePicLink){
if let data = NSData(contentsOf: url as URL) {
return UIImage(data: data as Data)!
}
}
return UIImage()
}
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 {
storageRef.downloadURL { url, error in
completion(url)
// success!
}
} else {
// failed
completion(nil)
}
}
}
Updated : I modifed my function uploadProfileImage as following. My point is I wanna assign profilePicLink variables to the downloadurl. And then I update value of profilePicLink
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)
}
}
)
}
}
}
Please be advised on this.

Wait for image to be uploaded before continuing function - Swift 5 Firebase Storage

I'm trying to upload an image using Firebase Storage. The problem i'm having is when i'm calling my image upload function, the upload task is very slow and it ends up running last in my main function when it is actually called further up.
Is there a way to wait for storageRef.putData to finish before continuing the rest of the function?
Would possibly reducing compression quality from 1 be faster?
Here is my function:
func newImageUrl(){
let key = self.itemId
guard let userID = Auth.auth().currentUser?.uid else { return }
let storageRef = Storage.storage().reference().child(userID).child("SubCategory").child(self.itemTitle!).child(key!).child("ItemImages.jpg")
guard let imageData = self.itemImage.image!.jpegData(compressionQuality: 1) else { return }
print("starting image upload!")
storageRef.putData(imageData, metadata: nil) { (metadata, error) in
guard let metadata = metadata else {
return
}
storageRef.downloadURL { (url, error) in
guard let urlStr = url else{
return
}
let urlFinal = (urlStr.absoluteString)
self.imageFinalUrl = urlFinal
}
}
}
UPDATED func --
func newImageUrl(completion:#escaping((String?) -> () )) {
let key = self.itemId
guard let userID = Auth.auth().currentUser?.uid else { completion(nil) ; return }
let storageRef = Storage.storage().reference().child(userID).child("SubCategory").child(self.itemTitle!).child(key!).child("ItemImages.jpg")
guard let imageData = self.itemImage.image!.jpegData(compressionQuality: 1) else { completion(nil) ; return }
print("starting image upload!")
storageRef.putData(imageData, metadata: nil) { (metadata, error) in
guard let metadata = metadata else {
return
}
storageRef.downloadURL { (url, error) in
guard let urlStr = url else{
completion(nil)
return
}
let urlFinal = (urlStr.absoluteString)
self.imageFinalUrl = urlFinal
completion(urlFinal)
}
}
}
Example main function:
func updateItemDetail(){
print("this is running 1")
self.newImageUrl { (str) in
print(str)
}
print("this is running 2")
Console prints:
this is running 1
starting image upload!
this is running 2
Optional("https://firebasestorage.googleapis.com/v0/b/.....etc")
You need a completion
func newImageUrl(completion:#escaping((String?) -> () )) {
let key = self.itemId
guard let userID = Auth.auth().currentUser?.uid else { completion(nil) ; return }
let storageRef = Storage.storage().reference().child(userID).child("SubCategory").child(self.itemTitle!).child(key!).child("ItemImages.jpg")
guard let imageData = self.itemImage.image!.jpegData(compressionQuality: 1) else { completion(nil) ; return }
print("starting image upload!")
storageRef.putData(imageData, metadata: nil) { (metadata, error) in
guard let metadata = metadata else {
return
}
storageRef.downloadURL { (url, error) in
guard let urlStr = url else{
completion(nil)
return
}
let urlFinal = (urlStr.absoluteString)
self.imageFinalUrl = urlFinal
completion(urlFinal)
}
}
}
Call
newImageUrl { (str) in
print(str)
// do your next work here
}