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.
Related
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.
In my app, I display some remote images or PDF files and want to give the user the ability to download them. In order to do so, I try to save them locally first in .documentDirectory before opening a UIDocumentInteractionController to handle the file.
However, I am having an issue, which is that even if the action sheet opens fine and proposes all the expected options, in the end I can never use the file because of an error. Specifically:
If I try to use the file in a mail, the mail opens but empty,
If I try to use it in Whatsapp, I get an error saying "The item cannot be shared. Please selected a different item."
And if I choose "Save to Files", the files action sheet briefly opens but closes immediately afterwards with an error in the console saying: [ShareSheet] cancelled request - error: The operation couldn’t be completed. Invalid argument
Here is the code I use to cache the remote file, then to open it with UIDocumentInteractionController:
URLSession.shared.downloadTask(with: url) { localUrl, response, error in
if let localUrl = localUrl {
do {
let imageData = try Data(contentsOf: localUrl)
let d = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last
if let docUrl = d?.appendingPathComponent(url.lastPathComponent) {
try imageData.write(to: docUrl)
self.download(docUrl)
}
} catch {
print("Oops: \(error)")
}
}
}.resume()
func download(_ url: URL) {
DispatchQueue.main.async {
let documentInteractionController = UIDocumentInteractionController()
documentInteractionController.delegate = self
documentInteractionController.url = url
documentInteractionController.uti = url.typeIdentifier ?? "public.data, public.content"
documentInteractionController.name = url.localizedName ?? url.lastPathComponent
documentInteractionController.presentOptionsMenu(from: self.view.frame, in: self.view, animated: true)
}
}
Thank you for your help
I can't tell you why it doesn't work with a UIDocumentInteractionController, but it does work with a UIActivityViewController.
private func download(_ url: URL)
{
DispatchQueue.main.async {
let avc = UIActivityViewController(activityItems: [url], applicationActivities: nil)
self.present(avc, animated: true, completion: nil)
}
}
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
}
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!
I am trying to download a video from my firebase storage. The way I am doing that is by using the .downloadURLWithCompletion function. When ever the function executes, I receive this error
Error Domain=FIRStorageErrorDomain Code=-13010 "Object videos/video1.m4v
does not exist." UserInfo={object=videos/video1.m4v
, bucket=**********.appspot.com, ResponseBody={
"error": {
"code": 404,
"message": "Not Found"
}
}, data=<7b0a2020 22657272 6f72223a 207b0a20 20202022 636f6465 223a2034 30342c0a 20202020 226d6573 73616765 223a2022 4e6f7420 466f756e 64220a20 207d0a7d>, NSLocalizedDescription=Object videos/video1.m4v
does not exist., ResponseErrorDomain=com.google.HTTPStatus, ResponseErrorCode=404}
I have changed my storage settings on firebase to allow unauthenticated access:
I have also checked to make sure that the storage link is correct:
Here is the code that is accessing the Firebase storage:
import UIKit
import AVKit
import AVFoundation
import FirebaseStorage
class VideoViewController: UIViewController
{
var videoUrl:NSURL!
var storageRef:FIRStorageReference!
override func viewDidLoad()
{
let storage = FIRStorage.storage()
storageRef = storage.referenceForURL("gs://**********.appspot.com")
let videosRef = storageRef.child("videos")
let videoName = NSUserDefaults.standardUserDefaults().objectForKey("videoName") as! String
videosRef.child(videoName).downloadURLWithCompletion { (URL, error) -> Void in
if (error != nil)
{
print(error!)
}
else
{
self.videoUrl = URL
do
{
try self.playVideo()
}
catch
{
print("Error")
}
}
}
super.viewDidLoad()
// Do any additional setup after loading the view.
}
So, I tried using a direct link and it worked!
override func viewDidLoad()
{
let storage = FIRStorage.storage()
storageRef = "gs://*************.appspot.com"
let videosRef = "videos"
let videoName = NSUserDefaults.standardUserDefaults().objectForKey("videoName") as! String
storage.referenceForURL("\(storageRef)/\(videosRef)/\(videoName)").downloadURLWithCompletion { (URL, error) in
if (error != nil)
{
print(error!)
}
else
{
self.videoUrl = URL
do
{
try self.playVideo()
}
catch
{
print("Error")
}
}
}
Of course, using a direct link for something like this isn't exactly the best way to get data. So next I compared the two links generated by printing them out. Here is how I printed the first link:
var videoUrl:NSURL!
var storageRef:FIRStorageReference!
override func viewDidLoad()
{
let storage = FIRStorage.storage()
storageRef = storage.referenceForURL("gs://*********.appspot.com")
let videosRef = storageRef.child("videos")
let videoName = NSUserDefaults.standardUserDefaults().objectForKey("videoName") as! String
print(videosRef.child(videoName))
and it printed
gs://***********.appspot.com/videos/video1.m4v
And the second link:
var videoUrl:NSURL!
var storageRef:String!
override func viewDidLoad()
{
let storage = FIRStorage.storage()
storageRef = "gs://***********.appspot.com"
let videosRef = "videos"
let videoName = NSUserDefaults.standardUserDefaults().objectForKey("videoName") as! String
print("\(storageRef)/\(videosRef)/\(videoName)")
What it printed
gs://***********.appspot.com/videos/video1.m4v
Now, I also tried printing the value of videoName to make sure that it was correct and every time that I printed it out it was video1.m4v
I banked out the link to my firebase storage, but I can assure you that the link is correct all around.
Can someone explain to me why I am getting this error? To me everything looks to be in place.
Thanks!
Try this -- if there is an issue with the underlying representation of a ref this may help:
instead of:
videosRef.child(videoName).downloadURLWithCompletion { (URL, error) -> Void in
do:
storage.referenceForURL(String(videosRef.child(videoName))).downloadURLWithCompletion { (URL, error) -> Void in
that is, does referenceForURL of the stringValue do something different than a direct call. It shouldn't -- if it does, it might have something to do with your videoName. Maybe it ends with a slash? Can you post the value of your videoName?
So, if I understand correctly, you want to download the image without passing the full URL path?
If so, I think downloadURLWithCompletion requires the full URL path.
I can't test this, since I don't have my data set up this way (I just store the full URLs to media files in firebase storage to my firebase database), but try this:
videosRef.child(videoName).dataWithMaxSize(INT64_MAX, completion: { (data, error) in
if let error = error {
print("Error downloading: \(error)")
return
}
cell.imageView?.image = UIImage.init(data: data!)
})
In your firebase storage, you haven't placed your video file inside a folder called videos.
And despite this you try to access to .../videos/filename which doesn't exist. Either try to remove the /videos from: gs://***********.appspot.com /videos /video1.m4v
or
Either create a folder called videos inside your firebase storage and then add the same video inside it with the same name (since you cant drag and drop files into other folders), or remove the:
let videosRef = "videos"
from your path.
Hope it helps.