Firebase multiple file uploading issue with Swift 3 - swift

So I am fairly new to swift and firebase, however I have come to realize that using firebase to sync data has created a particular issue for me. I have a very simple application that basically allows users to upload multiple images onto the firebase storage.
What is happening in my code now is that once the very first image that was uploaded is done, the view segues to the next view controller. What I want to do is to only have it segue when ALL of the images are uploaded not just one
What I have attempted to do is create a List of Floats and for every Image that the user intends to upload, I will only keep track of the very last Image uploaded. However, I have to believe that there is another way of doing this. Furthermore my solution does not seem to be working correctly.
func observeUploadProgress(for job: Job) {
let imageName = UUID().uuidString
let storageRef = FIRStorage.storage().reference().child("\(imageName).png")
let uploadData = UIImagePNGRepresentation(job.beforeImage!)
let uploadTask = storageRef.put(uploadData!, metadata: nil, completion: { (metadata, error) in
if error != nil {
print(error)
return
}
job.beforeImageUrl = (metadata?.downloadURL()?.absoluteString)!
self.uploadingImagesToFirebase = false
if (self.arrayProgressList[self.arrayProgressList.count - 1] >= 1.0) {
self.alertController?.dismiss(animated: true, completion: nil)
self.performSegue(withIdentifier: "unwindFromCustomerDetailsSegue", sender: self)
}
})
self.arrayProgressList.append(0.0)
let curr_index = self.arrayProgressList.count - 1
uploadTask.observe(.progress) { (snapshot) in
guard let progress = snapshot.progress else { return }
self.uploadingImagesToFirebase = true
self.arrayProgressList[curr_index] = Float(progress.fractionCompleted)
self.progressView?.progress = self.arrayProgressList[self.arrayProgressList.count - 1]
}
}

Related

Track progress of PHPickerViewController video upload, takes way too long to upload videos over 5 minutes

I set this up in my code and it works fine. I try to download the URL from user library, and upload it to Firebase Storage through another function, but for videos over 5 minutes, it seems to take forever to download before uploading to the database, to the point where it seems like nothing is happening or going to happen. Is there any way I can track the progress of the download? If not, how can I at least quicken the process?
extension postingRViewController: PHPickerViewControllerDelegate
{
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true, completion: nil)
progressWheel.startAnimating()
progressWheel.alpha = 1
for result in results {
print("made result")
var went = false
result.itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier)
{ videoURL, error in
result.itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier) { videoURL, error in
assert(Thread.isMainThread == false)
let directory = NSTemporaryDirectory()
let fileName = NSUUID().uuidString.appending(".mov")
print("loading")
went = true
if let videoURL = videoURL,
let copiedURLFile = NSURL.fileURL(withPathComponents: [directory, fileName]) {
try! FileManager.default.copyItem(at: videoURL, to: copiedURLFile)
DispatchQueue.main.async {
// the videourl is deleted. Only copiedURLFile exists
// upload_to_firebase(video url)
self.uploadVideo(videoURL: copiedURLFile)
print("uploaded to the cloud")
// after the video is presented or the file uploaded, delete copiedURLFile
}
}
else { print(error!) }
}
}
}
The issue happened due to nested loadFileRepresentation(forTypeIdentifier:).

user does not have permission to access gs:// xxx.appspot.com

In this application I created authencation, database with firebase. but in my post, which consists of title, content, and image, I only see my default image. I'm having a bit of a problem with storage in the database, first uploading the image to firebase storage and then taking its url.
import Foundation
import SwiftUI
import Firebase
import FirebaseStorage
class StorageStore: ObservableObject {
let storageRef = Storage.storage().reference()
func uploadImage(_ image: UIImage, completion: #escaping (URL?) -> Void) {
let imageRef = storageRef.child("images/"+timeString()+".jpg")
guard let imageData = image.jpegData(compressionQuality: 0.1) else {
return completion(nil)
}
let metadata = StorageMetadata()
metadata.contentType = "image/jpg"
imageRef.putData(imageData, metadata: metadata, completion: { [self] (metadata, error) in
if let error = error {
assertionFailure(error.localizedDescription) // 🛑
//Thread 1: Fatal error: User does not have permission to access gs://ios-post-193ec.appspot.com/images/2022-03-29T10:03:18Z.jpg.
return completion(nil)
}
imageRef.downloadURL(completion: { (url, error) in
if let error = error {
assertionFailure(error.localizedDescription)
return completion(nil)
}
completion(url)
})
})
}
func timeString() -> String {
let now = Date()
let formatter = ISO8601DateFormatter()
let datetime = formatter.string(from: now)
print(datetime)
return datetime
}
}
as a result, I can't upload the image to storage
In this case, I have an error (🛑) as shown above. instead of the default image, one of the 6 permanent images that would be in the simulator had to come out.
The error message shows that the user does not have permission to access the file. So it looks like you have security rules controlling who can access the files in your Cloud Storage through Firebase, and those rules reject this read operation. I recommend checking the documentation I linked (there's also an old, but still great, video in there) to learn how to allow the operation, while still keeping the files secure.

How to create a local Object from a CKRecord and append it to an array for NSTableView data

I'm working on a note app project for macOS with cloud capabilities, particularly downloading another person's note given the notes "CloudID" which is just the unique name which the cloud database gives the record when the note is uploaded to the cloud. I'm using an sqlite database for local data storage and a cloudKit database for public data storage. When I test the download feature it adds the data to the sqlite DB correctly but the local object which I append to my notesArray which is used for populating the NSTableView in my home page doesn't get modified.
Here's the code:
#IBAction func downloadNoteFromCloud(_ sender: Any) {
// 1.) check to make sure text field isn't empty
if (noteIDTextField.stringValue == "") {
noteIDTextField.stringValue = "Please enter the desired note's cloudID"
}
// 2.) query the note with the given cloudID
// make note object for the downloaded note bc of scope issues
var downloadedNote = Note(0, "", "")
//sets cloudDB to the public cloud database
let container = CKContainer.default()
let cloudDB = container.publicCloudDatabase
let noteRecordID = CKRecord.ID(recordName: noteIDTextField.stringValue)
// returns a CKRecord
cloudDB.fetch(withRecordID: noteRecordID) { record, error in
if let error = error {
// Handle error
print(error)
return
}
// Record fetched successfully
if let noteRecord = record {
// set the values of the downloadedNote obj equal the the values of the noteRecord
downloadedNote.setValues((notesArray.count), noteRecord["Title"] as! String, noteRecord["Body"] as! String)
// add to local database ~ this works
do {
let path = NSSearchPathForDirectoriesInDomains(
.applicationSupportDirectory, .userDomainMask, true
).first! + "/" + Bundle.main.bundleIdentifier!
// create parent directory iff it doesn’t exist
try FileManager.default.createDirectory(
atPath: path, withIntermediateDirectories: true, attributes: nil
)
//connect to DB
let db = try Connection("\(path)/db.sqlite3")
//Define the Notes Table and its Columns
let notes = Table("Notes")
//ID is primary key so it doesn't need to be defined for inserts
let title = Expression<String>("Title")
let body = Expression<String>("Body")
try db.run(notes.insert(title <- downloadedNote.title, body <- downloadedNote.body))
}
catch {
print(error)
}
}
}
// append downloadedNote to notesArray
notesArray.append(downloadedNote)
//Send Notification to reload the tableView data
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "load"), object: nil)
//Return to home page
self.view.window?.close()
}
I'm assuming it's a scope issue and I've tried moving the append func around and I've tried making the original downloadedNote declaration be an optional type which isn't initialized like so: var downloadedNote: Note? and then initializing it inside of the fetch func like so: downloadedNote = Note(id: notesArray.count, title: noteRecord["Title"] as! String, body: noteRecord["Body"] as! String) none of which have worked correctly. The closest I've gotten it to work is how the code is now. Please let me know where I went wrong and how to fix it. Thank you very much for any help!
CloudKit works asynchronously. Move the code to notify and reload the table into the closure
Replace
try db.run(notes.insert(title <- downloadedNote.title, body <- downloadedNote.body))
}
catch {
print(error)
}
}
}
// append downloadedNote to notesArray
notesArray.append(downloadedNote)
//Send Notification to reload the tableView data
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "load"), object: nil)
//Return to home page
self.view.window?.close()
with
try db.run(notes.insert(title <- downloadedNote.title, body <- downloadedNote.body))
DispatchQueue.main.async {
// append downloadedNote to notesArray
notesArray.append(downloadedNote)
//Send Notification to reload the tableView data
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "load"), object: nil)
//Return to home page
self.view.window?.close()
}
}
catch {
print(error)
}
}
}
And create also the new note inside the closure. There are no scope issues anymore.
Side note:
NSSearchPathForDirectoriesInDomains smells objective-c-ish and is outdated. Use the FileManager URL related API
do {
let fm = FileManager.default
let applicationSupportURL = try fm.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let applicationSupportBundleURL = applicationSupportURL.appendingPathComponent(Bundle.main.bundleIdentifier!)
if !fm.fileExists(atPath: applicationSupportBundleURL.path) {
// create parent directory iff it doesn’t exist
try fm.createDirectory(at: applicationSupportBundleURL, withIntermediateDirectories: true, attributes: nil)
}
let dbPath = applicationSupportBundleURL.appendingPathComponent("db.sqlite3").path
//connect to DB
let db = try Connection(dbPath)
...

Using Firebase Storage for Uploading Images

I can only upload a single photo to FirebaseStorage.
All the tutorials I have seen on youtube use FirebaseStorage to save user's profile pictures. However, I am trying to make a restaurant app where I should be able to keep several pictures and not only one. My code right now only allows me to keep one. As soon as I try to upload a second image, it erases the first one.
Here's my code:
#IBAction func agregarComidaFav(_ sender: Any) {
guard let imageData = imagen?.jpegData(compressionQuality: 0.4) else {
return
}
let storageRef = Storage.storage().reference(forURL: "gs://comidas-68043.appspot.com")
let storageProfileRef = storageRef.child("ComidasFavoritas")
let metadata = StorageMetadata()
metadata.contentType = "image/png"
storageRef.child("comidasFavoritas/png").putData(imageData, metadata: metadata) { (storageMetaData, error) in
if error != nil {
print(error?.localizedDescription ?? "")
return
}
}
storageProfileRef.downloadURL(completion: { (url, error) in
if let metaImageUrl = url?.absoluteString {
print(metaImageUrl)
self.imagenURL = metaImageUrl
}
})
var ref: DocumentReference? = nil
ref = db.collection("comidasFavoritas").addDocument(data: [
"imagenURL" : imagenURL
]) { err in
if let err = err {
print("Error agregando comida: \(err)")
} else {
let id = ref!.documentID
print("Comida agregado con ID: \(id)")
}
}
}
This line:
storageRef.child("comidasFavoritas/png").putData(...)
...guarantees that each time, the data is going to be written to "comidasFavoritas/png"
I don't know anything about how you're keeping track of your data/images in your app, but you'll probably need some way to keep track of an ID for that image. Then, when you store it, you could store it like:
storageRef.child("comidasFavoritas/png\(imageId)").putData(...)
You'd probably also want to store that ID in your database when you store the image URL.

Uploading images to firebaseStorage

I have been building an app during the past month where you can upload your favorite memories. Each memory has a photo, date, short description, and place where it developed.
So the user was able to add memories, I did the following function for the photo part:
private func agregarRecuerdoFirebase() {
guard let imagenSeleccionada = self.imagen else {
print("No hay imagen")
return
}
guard let imagenData = imagenSeleccionada.jpegData(compressionQuality: 0.4) else {
return
}
let storageRef = Storage.storage().reference(forURL: "gs://chinahub-68043.appspot.com")
let storageRecuerdos = storageRef.child("recuerdos").child("recuerdoss")
let metadata = StorageMetadata()
metadata.contentType = "image/png"
storageRecuerdos.putData(imagenData, metadata: metadata) { (storageMetaData, error) in
if error != nil {
print(error?.localizedDescription)
return
}
}
Everything is working fine except the uploading to Firebase Storage. After uploading one photo to the storage, when I want to upload a second one it won't let me. Instead, the second image will replace the first one. How could I create a unique ID every time I want to add a new memory so that I could store it under that ID into the Storage?? Or is there another way for being able to achieve what I want??
try to replace below line in your project
let storageRecuerdos = storageRef.child("recuerdos").child("recuerdoss\(UUID().uuidString)\(Date().timeIntervalSince1970)")