Can't Upload Video to Firebase Storage on iOS 13 - ios

Works perfectly fine on iOS 12.
Simple boilerplate code:
let storageRef = storage.reference().child("\(profile.studioCode)/\(selected.classId)/\(uploadDate)")
//Upload file and metadata
let uploadTask = storageRef.putFile(from: videoURL, metadata: metadata)
//Listen for state changes and, errors, and completion of the upload
uploadTask.observe(.resume) { (snapshot) in
//upload resumed or started
}
uploadTask.observe(.pause) { (snapshot) in
//upload paused
}
uploadTask.observe(.progress) { (snapshot) in
//upload progress
}
uploadTask.observe(.success) { (snapshot) in
//upload successful
}
uploadTask.observe(.failure) { (snapshot) in
//upload failed
}
Gives me:
Error Domain=FIRStorageErrorDomain Code=-13000 "An unknown error occurred, please check the server response."
I've updated Cocoapods and Firebase to the newest versions, tried allowing arbitrary loads, and tried signing out and back into the app to reset my auth token. In iOS 13 it throws that error immediately on upload, but on iOS 12 it uploads perfectly fine. Any help or insight would be greatly appreciated. Thanks!

I had a similar issue but here is an easy workaround: You need to use '.putData' instead of '.putFile' and specify the MIME type on upload.
let metadata = StorageMetadata()
//specify MIME type
metadata.contentType = "video/quicktime"
//convert video url to data
if let videoData = NSData(contentsOf: videoURL) as Data? {
//use 'putData' instead
let uploadTask = storageRef.putData(videoData, metadata: metadata)
}

How I ended up fixing it:
It turns out that file paths are different in iOS 13 than iOS 12:
iOS12 path:
file:///private/var/mobile/Containers/Data/Application/DF9C58AB-8DCE-401B-B0C9-2CCAC69DC0F9/tmp/12FD0C43-F9A0-4DCB-96C3-18ED83FED424.MOV
iOS13 path:
file:///private/var/mobile/Containers/Data/PluginKitPlugin/5DFD037B-AC84-463B-84BD-D0C1BEC00E4C/tmp/trim.7C8C6CD1-97E7-44D4-9552-431D90B525EA.MOV
Note the extra '.' in the iOS13 path. My solution was to, inside of my imagePickerController didFinishPickingMediaWithInfo function, copy the file into another temp directory, upload it from there, and then delete the copy.
do {
if #available(iOS 13, *) {
//If on iOS13 slice the URL to get the name of the file
let urlString = videoURL.relativeString
let urlSlices = urlString.split(separator: ".")
//Create a temp directory using the file name
let tempDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
let targetURL = tempDirectoryURL.appendingPathComponent(String(urlSlices[1])).appendingPathExtension(String(urlSlices[2]))
//Copy the video over
try FileManager.default.copyItem(at: videoURL, to: targetURL)
picker.dismiss(animated: true) {
self.videoRecorded = false
self.showUpload(targetURL)
}
}
else {
//If on iOS12 just use the original URL
picker.dismiss(animated: true) {
self.videoRecorded = false
self.showUpload(videoURL)
}
}
}
catch let error {
//Handle errors
}

Related

Firebase Storage Upload Error in Share Extensions

I'm creating an app with App Extension and I'm having trouble uploading files to firebase storage.
I choose a photo that I chose from the Photos application or another application for my own application by pressing the share button.
I am getting the URL of the selected photo in ShareViewController.
if let items = (self.extensionContext?.inputItems.first as? NSExtensionItem)?.attachments {
let contentType = kUTTypeData as String
for item in items {
item.loadItem(forTypeIdentifier: contentType, options: nil) { url, error in
if let path = url as? NSURL {
URLs.append(path.absoluteString!)
}
else {}
}
}
}
I'm trying to upload the URLs I get this way to firebase storage.
let uploadTask = riversRef.putFile(from: URLs[0], metadata: nil) { metadata, error in
guard let metadata = metadata else {
return
}
...
}
There is a problem with the URL of the selected photo but I can't quite understand it. I can get the size, name and other properties of the photo from the URL. I can even copy this photo to another directory with FileManager. I'm getting the URL from FileManager but still getting the same error. But when I try to install I get the following error.
Error Domain=FIRStorageErrorDomain Code=-13000 "An unknown error occurred, please check the server response." UserInfo={bucket=appBucketName, _NSURLErrorFailingURLSessionTaskErrorKey=BackgroundUploadTask <9381D3C6-241C-4737-9589-BA5A7CFAF9E4>.<1>, object=PATH/6B36F026-CD6E-4E00-8A19-C530DC606674.jpg, _NSURLErrorRelatedURLSessionTaskErrorKey=(
"BackgroundUploadTask <9381D3C6-241C-4737-9589-BA5A7CFAF9E4>.<1>"
), NSLocalizedDescription=An unknown error occurred, please check the server response., ResponseErrorDomain=NSURLErrorDomain, ResponseErrorCode=-995}
Solutions I tried;
I will not use putData.
I tried App Groups. I already have the URL of the file. I need to install directly but it doesn't work.
extension FileManager {
func documentsDirectory() -> URL {
let path = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.name")
return path!
}
func uploadPath(srcURL: URL, name: String, completion: #escaping ((_ filePath: URL?) -> Void)) {
do {
let path = documentsDirectory().appendingPathComponent("tempory")
try FileManager.default.createDirectory(atPath: path.relativePath, withIntermediateDirectories: true, attributes: nil)
let fullPath = path.appendingPathComponent(name)
try FileManager.default.copyItem(at: srcURL, to: fullPath)
completion(fullPath)
}
catch {
completion(nil)
}
}
}
Note: I am using Firebase emulator and Xcode simulator. I don't have any problems with the main application.
If there is missing information or incorrect information, please warn, I will correct it. Thank you for your help in advance.

Download a pdf from Firebase and store it locally on iOS

I need to download a pdf from the storage and save it locally on an iOS device, so it can be seen in Files.
Here is the code is taken from the docs, which I'm using:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let userID = Auth.auth().currentUser!.uid
print(userID)
// Get a reference to the storage service using the default Firebase App
let storage = Storage.storage()
// Create a storage reference from our storage service
let storageRef = storage.reference()
// Create a reference to the file you want to download
let islandRef = storageRef.child("pdf/sample.pdf")
// Create local filesystem URL
let localURL = URL(string: "pdf/sample.pdf")!
// Download to the local filesystem
let downloadTask = islandRef.write(toFile: localURL) { url, error in
if let error = error {
// Uh-oh, an error occurred!
} else {
// Local file URL for "images/island.jpg" is returned
}
}
}
When I try to run this ViewController, it doesn't crash but throws the following error:
"The file couldn’t be opened because the specified URL type isn’t supported." UserInfo={NSURL=pdf/sample.pdf}
The file in the Firebase Storage is saved in a folder called pdf/sample.pdf. Eventually, I wish to take the reference from the storage and pass it in a RealtimeDatabase, so the user can download it by viewing details about it in a table view.
I think what need to do is to specify in which path to your local filesystem you want to save the downloaded document. So let say you want to use the temporary folder to save your pdf. You can try the following:
let tmporaryDirectoryURL = FileManager.default.temporaryDirectory
let localURL = tmporaryDirectoryURL.appendingPathComponent("sample.pdf")
islandRef.write(toFile: localURL) { url, error in
if let error = error {
print("\(error.localizedDescription)")
} else {
self.presentActivityViewController(withUrl: url)
}
}
Once the file is downloaded in order to save it in the Files app you will need to use UIActivityViewController.
func presentActivityViewController(withUrl url: URL) {
DispatchQueue.main.async {
let activityViewController = UIActivityViewController(activityItems: [url], applicationActivities: nil)
activityViewController.popoverPresentationController?.sourceView = self.view
self.present(activityViewController, animated: true, completion: nil)
}
}
I haven't tested it but my assumption is that you get this error because your localURL variable is not a filesystem URL.
Instead of using URL(string: String) you should be using URL(fileURLWithPath: String) when opening files.

iOS Share extension - the loadItemForTypeIdentifier call returns file url in iOS11 and file content in iOS12

I see different behavior on iOS 11 vs 12.
On iOS 11 - I get the filepath of files shared in completion handler.
On iOS 12 - I get a URL domain error. But if i handle it based on the type (eg: UIImage), then I get the file content.
Is this behaviour only on simulator or on device as well ?
Do we need to handle this per iOS version ?
Yes you will get both thing (file path or data) on device also. You did not need to add any check on iOS version.
Please flow my code. It is in swift but you can understand it.
func share() {
let inputItem = extensionContext!.inputItems.first! as! NSExtensionItem
let attachment = inputItem.attachments!.first as! NSItemProvider
if attachment.hasItemConformingToTypeIdentifier( kUTTypeImage as String) {
attachment.loadItem(forTypeIdentifier: kUTTypeImage as String, options: [:]) { (data, error) in
var image: UIImage?
if let someURl = data as? URL {
image = UIImage(contentsOfFile: someURl.path)
}else if let someImage = data as? UIImage {
image = someImage
}
if let someImage = image {
guard let compressedImagePath = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first?.appendingPathComponent("shareImage.jpg", isDirectory: false) else {
return
}
let compressedImageData = UIImageJPEGRepresentation(someImage, 1)
guard (try? compressedImageData?.write(to: compressedImagePath)) != nil else {
return
}
}else{
print("bad share data")
}
}
}
}

ImageView to Firebase Storage in Swift

I am trying to upload an image from ImageView to Firebase storage but it won't work.
I have listed my code below: My image view is called ProfileImage
let storageRef = Storage.storage().reference().child("myImage.png")
if let uploadData = self.ProfileImage.image!.pngData() {
storageRef.putFile(from: uploadData, metadata: nil) { (metadata, error) in
if error != nil {
print("error")
completion(nil)
} else {
// your uploaded photo url.
}
}
}
It comes up with the error "Cannot convert value of type 'Data' to expected argument type 'URL'
You are trying to upload Data, not a file. Replace
putFile
With
putData
And it should work fine
Try this code :-
let storageRef = Storage.storage().reference().child("myImage.png")
if let uploadData = UIImagePNGRepresentation(self.profileImage.image!) {
storageRef.put(uploadData, metadata: nil) { (metadata, error) in
if error != nil {
print("\(error.localizeDescription)")
} else {
// your uploaded photo url.
}
}
let refDatabase = Database.database().reference()
var refstorage = Storage.storage().reference()
let data = image.jpegData(compressionQuality: 1.0) //
let metadata = StorageMetadata()
metadata.contentType = "image/jpeg"
let postkey = refDatabase.child("Post").childByAutoId().key
print(postkey)
let imagename = "PostImage/\(postkey).png"
refstorage = refstorage.child(imagename)
let timestamp = Date().timeIntervalSince1970 // you can use this to track time when it was uploaded.
self.refstorage?.putData(data!, metadata: metadata, completion: { (meta, error) in
if error == nil{
if let imageData = meta?.downloadURL()?.absoluteString{
// DO SOMETHING
} else {
print("Couldn't get the URL for image")
}
}
})
I know that this question has been answered, but there is an easier way to do this. Along with import Firebase and Firebase Storage, you will also have to add FirebaseUI to your podfile and import it.
After you have done that, you could get your image to your app much simpler.
let storage = Storage.storage()
let storageRef = storage.reference()
let placeholderImage = UIImage(named: "placeholder.jpeg")
let reference = storageRef.child("myImage.png")
ProfileImage.sd_setImage(with: reference, placeholderImage: placholderImage)
(The placeholder Image would just be a transparent image that you put in your assets folder in XCode that you could reuse multiple times in your application, for whenever you needed to get a Firebase Image on your app.)

Using Firebase Storage's putFile() method is resulting in: The file “fileName” couldn’t be opened error

Here are two ways I've tried to upload the file:
1.
getURLOfPhoto(assetURL: imagesDictionary[String(whichProfileImage)]! , completionHandler: { (responseURL) in
FIRStorage.storage().reference().putFile(responseURL as! URL)
})
2.
let assets = PHAsset.fetchAssets(withALAssetURLs: [imagesDictionary[String(whichProfileImage)] as! URL], options: nil)
let asset = assets.firstObject
asset?.requestContentEditingInput(with: nil, completionHandler: { (contentEditingInput, info) in
let imageFile = contentEditingInput?.fullSizeImageURL?
FIRStorage.storage().reference().child("test").putFile(imageFile!, metadata: nil) { (metadata, error) in
if let error = error {
return
}
}
})
I am getting this error:
Body file is unreachable: /var/mobile/Media/DCIM/100APPLE/picture.JPG
Error Domain=NSCocoaErrorDomain Code=257 "The file “picture.JPG” couldn’t be opened because you don’t have permission to view it."
UserInfo={NSURL=file:///var/mobile/Media/DCIM/100APPLE/picture.JPG, NSFilePath=/var/mobile/Media/DCIM/100APPLE/picture.JPG,
NSUnderlyingError=0x15da49a0 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}
The URL seems to be being retrieved successfully and the error only occurs when the putFile() method gets called.
Does anyone know how to fix this error or another way of uploading a file (not a Data object) to Firebase Storage?
Thanks in advance
Currently Firebase Storage is unable to use file URLs that are retrieved using the PHAsset based code I used in my question (or at least it was't able to in my experience) - even if those files are files the user took with the camera on their own iPhone. So, one solution is to re-save the file in question to a location which is accessible to the Firebase Storage API and then upload the file by passing in that location's URL in to the putFile() method.
You can use this method if you're using the imagePickerController() method:
do {
let documentsURL = FileManager.default().urlsForDirectory(.documentDirectory,
inDomains: .userDomainMask)[0]
let fileURL = try documentsURL.appendingPathComponent("fileName.jpg")
let image = info[UIImagePickerControllerOriginalImage]
try UIImageJPEGRepresentation(image as! UIImage,1.0)?.write(to: fileURL, options: [])
FIRStorage.storage().reference().child("exampleLocation")
.putFile(fileURL, metadata: nil) { (metadata, error) in
if let error = error {
print("Error uploading: \(error.description)")
return
}
}
}
catch {
print("error is ", error)
}
It's possible that our uploader doesn't have the correct permissions to access that file due to the app sandbox (and we're pretty hesitant to grant broad file system access permissions).
I only recommend storing files in Documents/ and tmp/ per https://developer.apple.com/library/ios/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html
Granted, if it's coming from system resources, we might want to revisit that behavior. Typically I just do (yes, I know it's data instead of file and thus will have worse memory behavior):
// pragma mark - UIImagePickerDelegate overrides
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
// Get local image
guard let image: UIImage = info[UIImagePickerControllerOriginalImage] as? UIImage else { return }
let imageData = UIImagePNGRepresentation(image)!
// Get a reference to the location where we'll store our photos
let photosRef = storage.reference().child("chat_photos")
// Get a reference to store the file at chat_photos/<FILENAME>
let photoRef = photosRef.child("\(NSUUID().UUIDString).png")
// Upload file to Firebase Storage
let metadata = FIRStorageMetadata()
metadata.contentType = "image/png"
photoRef.putData(imageData, metadata: metadata).observeStatus(.Success) { (snapshot) in
// When the image has successfully uploaded, we get it's download URL
let text = snapshot.metadata?.downloadURL()?.absoluteString
}
// Clean up picker
dismissViewControllerAnimated(true, completion: nil)
}
#Mike McDonald, thanks for your answer it worked for me. I was having the exact same issue and was able to solve with your suggestions. Here is my code:
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
guard let image: UIImage = info[UIImagePickerControllerOriginalImage] as! UIImage else { return }
let profileImageName = "profileImageName.png"
let imageData = UIImagePNGRepresentation(image)!
let filePath = "\(FIRAuth.auth()!.currentUser!.uid)/\(Int(NSDate.timeIntervalSinceReferenceDate() * 1000))"
let photoStorageRef = FIRStorage.storage().reference().child(filePath)
let photoRef = photoStorageRef.child("\(profileImageName)")
let metadata = FIRStorageMetadata()
metadata.contentType = "image/png"
photoRef.putData(imageData, metadata: metadata) { metadata, error in
if let error = error {
print("Error uploading:\(error.localizedDescription)")
return
} else {
guard let downloadURL = metadata!.downloadURL() else { return }
guard let downloadURLString = metadata!.downloadURL()?.absoluteString else { return }
//do what I need to do with downloadURL
//do what I need to do with downloadURLString
}
}
Hope this can help anyone else having the same issue!

Resources