Why is NSData(contentsOfURL: url) always nil? - swift

I download a file from the DocumentPicker (iCloud, dropbox, etc.). This file can be .pages, .docx, .pdf,...
When I want to convert it to NSData for file upload, I always receive a nil value:
if NSFileManager.defaultManager().fileExistsAtPath(urlToFile!.path!) {
let data = NSData(contentsOfURL: urlToFile!) // initializer returns nil, although file and path exist
println(data) // prints nil
}
else {
println("Does not exist")
}
I cannot figure out why it is nil, the file exists at this path!
Where is my mistake?
Some more information
A screenshot, showing that it is not a scope problem, but the initializer (as stated in my question-title) already returns nil. In this example I use a suggestion from an answer below, utilizing contentsOfFile
Utilizing an ErrorPointer
"The operation couldn’t be completed. (Cocoa error 257.)" UserInfo=0x7fac556cbf00 {NSFilePath=SUPERLONGPATH<NSUnderlyingError=0x7fac570beb20 "The operation couldn’t be completed. Permission denied"})
File's origin
The file is downloaded via an DocumentPicker:
let docPicker = UIDocumentPickerViewController(documentTypes: ["public.content"], inMode: UIDocumentPickerMode.Import)
docPicker.delegate = self
docPicker.modalPresentationStyle = UIModalPresentationStyle.OverCurrentContext
self.presentViewController(docPicker, animated: true, completion: nil)
func documentPicker(controller: UIDocumentPickerViewController, didPickDocumentAtURL url: NSURL) {
labelDatei.text = "Bereit"
labelDateiChose.text = url.lastPathComponent
urlToFile = url
}
Solution
As it happens .pages and .numbers "files" are no files, but folders(containers). So they cannot be uploaded. Using a .pdf works now. Thanks to all for help

It looks scope problem. You're defining your local variable data inside the braces and it won't exist after the braces.
Get rid of the word let.
That way you will be assigning to a variable data from the enclosing scope

Related

How do I write to a local file in Swift/XCTest?

My ultimate question is about saving a screenshot from an AppleTV application using XCTest and Swift4 (running on a MacBook paired to the TV device), but I'm having trouble even writing a simple text string to a local file. If I can get this simple file-save working, I'm hoping I can resolve the screenshot issue. (Apologies for making this look like two questions but they appear to be related and resulted from my troubleshooting efforts.)
First, here's what I'm trying to do with a screenshot, based on sample code I found somewhere online:
let appshot = XCUIApplication().windows.firstMatch.screenshot()
let shotpath = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask)[0].appendingPathComponent("appshot.png")
let shotpathUrl = URL(string: "file://\(shotpath)")
print("Saving to: \(shotpath)")
do {
try appshot.pngRepresentation.write(to: shotpathUrl!)
} catch {
print("Failed saving screenshot due to \(error)")
}
This gives me the following output:
Saving to: file:///var/mobile/Containers/Data/Application/77D52C66-353B-4029-97D5-48E6BAE35C92/Downloads/appshot.png
Failed saving screenshot due to Error Domain=NSCocoaErrorDomain Code=4 "The file “appshot.png” doesn’t exist." UserInfo={NSFilePath=///var/mobile/Containers/Data/Application/77D52C66-353B-4029-97D5-48E6BAE35C92/Downloads/appshot.png, NSUnderlyingError=0x1c405bc60 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}}
Of course, the file doesn't exist because that's the file I'm trying to create. But /var/mobile doesn't exist on my laptop either -- it looks like the path FileManager is building may exist on the AppleTV device, but I want it on my laptop where my test script is executing.
So I backed out to a much more simple case, and even this is giving me problems:
let str = "This is a test"
let path = "file:///Users/haljor/foo.txt"
let pathUrl = URL(string: path)!
print("Path: \(path)")
print("URL: \(pathUrl)")
do {
try str.write(to: pathUrl, atomically: true, encoding: .utf8)
} catch {
print("Caught error writing to \(pathUrl): \(error)")
}
And here's the output:
Path: file:///Users/haljor/foo.txt
URL: file:///Users/haljor/foo.txt
Caught error writing to file:///Users/haljor/foo.txt: Error Domain=NSCocoaErrorDomain Code=4 "The folder “foo.txt” doesn’t exist." UserInfo={NSURL=file:///Users/haljor/foo.txt, NSUserStringVariant=Folder, NSUnderlyingError=0x1c40553f0 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}}
Here, it looks like it's trying to write to a folder at the path I specified, not a file. Clearly there's something I'm not understanding in each of these cases.
I don't really have a preference for whether I use a fully-specified path or something using FileManager -- it just needs to land somewhere on my laptop (not the TV device). What am I missing?
You can add an attachment to the test case and save it to disk too. The problem was that Downloads folder may not exist in the container yet. The best way to handle this is via init-once property:
var downloadsFolder: URL = {
let fm = FileManager.default
let folder = fm.urls(for: .downloadsDirectory, in: .userDomainMask)[0]
var isDirectory: ObjCBool = false
if !(fm.fileExists(atPath: folder.path, isDirectory: &isDirectory) && isDirectory.boolValue) {
try! fm.createDirectory(at: folder, withIntermediateDirectories: false, attributes: nil)
}
return folder
}()
func test() {
let appshot = XCUIScreen.main.screenshot()
let attachment = XCTAttachment(screenshot: appshot)
attachment.lifetime = .keepAlways
self.add(attachment)
// Save to container
let url = downloadsFolder.appendingPathComponent("appshot.png")
try! appshot.pngRepresentation.write(to: url)
}
If you want to view the attachment, right-click on the test case, select Jump to Report and expand the tree. You will see the screenshot eventually:

Move File After Downloading

Im using parse to download some files to the device and all is good but i want to then move the file from the default cache directory which is where parse stores them (/Library/Caches/PFFileCache/) to somewhere more stable say the users documents directory.
The localised error i am getting is:
Error: “7b54d8a0f1a64b710058d4408ca4d696_The%20Name%20of%20the%20Wind%2029-92.mp3” couldn’t be moved to “Documents” because either the former doesn't exist, or the folder containing the latter doesn't exist.
But I'm sure both exist. It may be something to do with the name as when i ge the name from the PFFile it doesn't have the encoded %20 in the file name.
This is my code block:
let cachedPFFile = object.object(forKey: "partAudio") as! PFFile
print(cachedPFFile)
let getCachedFilePath = cachedPFFile.getPathInBackground()
getCachedFilePath.waitUntilFinished()
let cachedFilePath = getCachedFilePath.result as! String
print(cachedFilePath)
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentsDirectory = String(describing: paths[0])
let saveDirectory = documentsDirectory.appending(cachedPFFile.name)
print(documentsDirectory)
print(saveDirectory)
let fileManager = FileManager.default
if fileManager.fileExists(atPath: saveDirectory) == false {
do {
try fileManager.moveItem(atPath: cachedFilePath, toPath: saveDirectory)
print("Move successful")
} catch let error {
print("Error: \(error.localizedDescription)")
}
}
This is the entire log:
<PFFile: 0x608000458f00>
2017-02-20 18:42:35.430 ParseStarterProject-Swift[2260:55934] Warning: A long-running operation is being executed on the main thread.
Break on warnBlockingOperationOnMainThread() to debug.
/Users/Genie/Library/Developer/CoreSimulator/Devices/A2FB00CE-B018-4FDF-9635-35FD6678DF8D/data/Containers/Data/Application/BA7C112C-BECB-4734-8C67-A9CB84F0E1F3/Library/Caches/Parse/PFFileCache/7b54d8a0f1a64b710058d4408ca4d696_The%20Name%20of%20the%20Wind%2029-92.mp3
file:///Users/Genie/Library/Developer/CoreSimulator/Devices/A2FB00CE-B018-4FDF-9635-35FD6678DF8D/data/Containers/Data/Application/BA7C112C-BECB-4734-8C67-A9CB84F0E1F3/Documents/
file:///Users/Genie/Library/Developer/CoreSimulator/Devices/A2FB00CE-B018-4FDF-9635-35FD6678DF8D/data/Containers/Data/Application/BA7C112C-BECB-4734-8C67-A9CB84F0E1F3/Documents/7b54d8a0f1a64b710058d4408ca4d696_The Name of the Wind 29-92.mp3
Error: “7b54d8a0f1a64b710058d4408ca4d696_The%20Name%20of%20the%20Wind%2029-92.mp3” couldn’t be moved to “Documents” because either the former doesn't exist, or the folder containing the latter doesn't exist.
I am confused by this because both the file and the new directory exist??
though I'm not sure about the "file:///Users" as this doesn't work in finder, have to use the true path "/Users" how can i remove "file://" from the start of the string??
Also I've been searching SO without luck on how to add the %20 in the spaces of the file name none of the obvious options seem to work.
You have threading issues. Your entire idea of "waitUntilFinished" is wrong. You cannot block the main thread — and you are getting a warning telling you so. Listen to that warning!
Do not call getFilePathInBackground, and do not wait for the download to finish. Instead, call getFileInBackgroundWithBlock:, and inside the block proceed with the rest of your code, i.e. move the file.

How to Use SwiftyDropbox's "destination" with a Download

In reviewing the SwiftyDropbox tutorial in the v2 Dropbox API, it shows how to perform a download:
// Download a file
let destination : (NSURL, NSHTTPURLResponse) -> NSURL = { temporaryURL, response in
let fileManager = NSFileManager.defaultManager()
let directoryURL = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
// generate a unique name for this file in case we've seen it before
let UUID = NSUUID().UUIDString
let pathComponent = "\(UUID)-\(response.suggestedFilename!)"
return directoryURL.URLByAppendingPathComponent(pathComponent)
}
client.files.download(path: "/MyFile.db", destination: destination).response { response, error in
if let (metadata, url) = response {
print("*** Download file ***")
let data = NSData(contentsOfURL: url)
print("Downloaded file name: \(metadata.name)")
print("Downloaded file url: \(url)")
print("Downloaded file data: \(data)")
} else {
print(error!)
}
}
I'm unclear what's going on with the destination part. Why do I need to generate a random string for the filename?
When I try to specify my own filename, the download doesn't seem to work:
let destination : (NSURL, NSHTTPURLResponse) -> NSURL = { temporaryURL, response in
let directoryURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
return directoryURL.URLByAppendingPathComponent("MyFile.db")
}
I want to download a file from Dropbox named MyFile.db and I want to put it in my device's documents directory with the name MyFile.db and overwrite it if it's already there.
How can I do that?
When you say it doesn't seem to work, I expect you mean you get an error like this:
Error Domain=NSCocoaErrorDomain Code=516 "“CFNetworkDownload_bPYhu1.tmp” couldn’t be moved to “Documents” because an item with the same name already exists." UserInfo={NSSourceFilePathErrorKey=..., NSUserStringVariant=(
Move
), NSDestinationFilePath=..., NSUnderlyingError=0x7fda0a67cea0 {Error Domain=NSPOSIXErrorDomain Code=17 "File exists"}}
SwiftyDropbox, by virtue of using AlamoFire, doesn't currently let you overwrite files using the download function.
Specifically, SwiftyDropbox calls download in AlamoFire, and AlamoFire calls NSFileManager.moveItemAtURL. The documentation for NSFileManager.moveItemAtURL says:
If an item with the same name already exists at dstURL, this method aborts the move attempt and returns an appropriate error.
So, it seems like it's just being cautious, and making it hard for your app to accidentally overwrite (ad potentially lose) data. If you definitely know you want to overwrite a particular file, you'll need to do so explicitly, after the Dropbox API call. We'll consider this a feature request though.
Update: SwiftyDropbox now offers the ability to overwrite the files directly as of version 3.1.0, e.g., using download(path:rev:overwrite:destination:).

Uploading a video to S3 with swift The operation couldn’t be completed. (Cocoa error 260.)"

I am trying to upload a video file to Amazon S3
I constantly get an error 260:
Error in uploading the video: Error
Domain=NSCocoaErrorDomain Code=260 "The operation couldn’t be
completed. (Cocoa error 260.)
I read somewhere that amazon does not support asset library - is it true? and if so what do you suggest
Thanks Eran
func saveVideoToS3 () {
var uploadRequest: AWSS3TransferManagerUploadRequest = AWSS3TransferManagerUploadRequest()
uploadRequest.bucket = "BucketName"
uploadRequest.key = "KeyText"
//Move video file to the application folder so it can be read
var savedVideoURLToBeUsed = NSUserDefaults.standardUserDefaults().objectForKey("ThisIsTheVideoIWantToUse") as! String
println("Video saved in Store: \(savedVideoURLToBeUsed)")
var url: NSURL = NSURL(fileURLWithPath: savedVideoURLToBeUsed)!
uploadRequest.body = url
//uploadRequest.body = NSURL(fileURLWithPath: "file:///\(url)")
println("URL: \(url)")
let transferManager: AWSS3TransferManager = AWSS3TransferManager.defaultS3TransferManager()
transferManager.upload(uploadRequest).continueWithExecutor(AWSExecutor.mainThreadExecutor(), withBlock: { (AWSTask) -> AnyObject! in
//Handle errors
if AWSTask.error != nil {
println("Error in uploading the video: \(AWSTask.error)")
if AWSTask.error.code == 1001 {
self.saveVideoToS3()
}
// Retrive information important for later downloading
} else {
println("Video upload successful..")
var uploadResult: AnyObject! = AWSTask.result
println("Upload result: \(uploadResult)")
//Delete file from the application folder
}
return nil
})
}
Cocoa error 260 is a NSFileReadNoSuchFileError, meaning the path you specified is not valid (file is just not where you say it is), so it probably has nothing with S3 itself. There are three things why this is happening that come to my mind:
you did not use .synchronize() when saving the key to user settings
your file URL contains invalid characters
you did not write the file into filesystem properly
iOS8 Breaking change
Also please note that as of iOS8, due to changes how application work with their assigned sandboxes, you can't save absolute URL to file because next time you open application, it will be different.
Beginning in iOS 8, the Documents and Library directories are no
longer siblings of your application's bundle.
I am using two quick convenience functions that I wrote to get file from cache directory:
func cachePathWithFileName(fileName : String) -> String {
let cacheDirectoryPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as! String
return cacheDirectoryPath.stringByAppendingPathComponent(fileName)
}
and documents directory:
func documentsPathWithFileName(fileName : String) -> String {
let documentsDirectoryPath = NSSearchPathForDirectoriesInDomains(.CachesDirectory, .UserDomainMask, true)[0] as! String
return documentsDirectoryPath.stringByAppendingPathComponent(fileName)
}
Hopefully some of this tips help!

Why can't I append a String to a NSURL?

Appending the .txt file component to the URL path doesn't work:
var error:NSError?
let manager = NSFileManager.defaultManager()
let docURL = manager.URLForDirectory(.DocumentDirectory, inDomain:.UserDomainMask, appropriateForURL:nil, create:true, error:&error)
docURL.URLByAppendingPathComponent("/RicFile.txt") <-- doesn't work
via debugger:
file:///Users/Ric/Library/Developer/CoreSimulator/Devices/
<device id>/data/Containers/Data/Application/<app id>/Documents/
Writing a String using docURL to a file doesn't work because of the missing file name.
Reason (via error):
"The operation couldn’t be completed. Is a directory"
So Question: Why doesn't the following work?
docURL.URLByAppendingPathComponent("/RicFile.txt")
URLByAppendingPathComponent: doesn't mutate the existing NSURL, it creates a new one. From the documentation:
URLByAppendingPathComponent: Returns a new URL made by appending a
path component to the original URL.
You'll need to assign the return value of the method to something. For example:
let directoryURL = manager.URLForDirectory(.DocumentDirectory, inDomain:.UserDomainMask, appropriateForURL:nil, create:true, error:&error)
let docURL = directoryURL.URLByAppendingPathComponent("/RicFile.txt")
Even better would be to use NSURL(string:String, relativeTo:NSURL):
let docURL = NSURL(string:"RicFile.txt", relativeTo:directoryURL)
With the update to the Swift language, the suggested call to manager.URLForDirectory(...) no longer works because the call can throw (an exception). The specific error is "Call can throw, but it is not marked with 'try' and the error is not handled". The throw can be handled with the following code:
let directoryURL: NSURL?
do
{
directoryURL = try manager.URLForDirectory(.DocumentationDirectory,
inDomain: .UserDomainMask, appropriateForURL: nil, create: true)
}
catch _
{
print("Error: call to manager.URLForDirectory(...) threw an exception")
}