NSURLDownload: Assertion failed ([path isAbsolutePath]) troubles - macos

I'm trying to download a file off the internet and place it in the application name directory under the Application Support directory and I keep getting a
Assertion failed: ([path isAbsolutePath]), function -[NSURLDownload setDestination:allowOverwrite:], file /SourceCache/CFNetwork/CFNetwork-720.5.7/Foundation/NSURLDownload.mm, line 370.
Here's the code that I wrote:
var imageRequest = NSURLRequest(URL: self.source)
var imageDownload = NSURLDownload(request: imageRequest, delegate:self)
var error: NSError? = NSError()
/* does path exist */
let directoryPath = self.destination.stringByDeletingLastPathComponent
let fileMgr = NSFileManager();
fileMgr.createDirectoryAtPath(directoryPath, withIntermediateDirectories: true, attributes: nil, error: &error)
imageDownload.setDestination(self.destination, allowOverwrite: true);
When I step through the code everything looks correct. self.source is
(https:/remoteDomain.com/img/downloadimage.jpg)
a NSURL
self.destination is the full path in my system (file:/Users/ryan/Library/Application%20Support/AppName/downloadimage.jpg)
Any Ideas?

To answer the question to your specific topic:
The error message says that your path is invalid. The right way to create the path for your image is the following:
let fileManager = NSFileManager.defaultManager()
var folder = "~/Library/Application Support/[APPNAME]/someFolder" as NSString
folder = folder.stringByExpandingTildeInPath
if fileManager.fileExistsAtPath(folder as String) == false {
do {
try fileManager.createDirectoryAtPath(folder as String, withIntermediateDirectories: true, attributes: nil)
}
catch {
//Deal with the error
}
}
BUT
#jtbandes is right. You should use NSURLSessionDownloadTask to download your files.
It is part of the Foundation.framework, which is available on OS X, iOS and watchOS.
The reason to use it is that Apple keeps updating this Api to meet the latest standards. For example, you don't need to worry about IPv4 or IPv6 etc. This avoids crashes and weird behavior in your app.
This is how you use it (Swift):
var imageRequest = NSURLRequest(URL: self.source)
let session = NSURLSession.sharedSession()
let downloadTask = session.downloadTaskWithRequest(imageRequest) { (url: NSURL?, response: NSURLResponse?, error: NSError?) -> Void in
//Work with data
}
downloadTask.resume()
Note that url is the path to the downloaded image.

Related

How do I read a text-file's content from it's shared Dropbox link in Swift 4.2 (without downloading)?

I'm having a hard time figuring out how to output a simple text-file's content from it's shared Dropbox link (without downloading) through Swift 4.2.
For Example:
let url = URL(string: "https://www.dropbox.com/s/rokwv82h54ogwy1/test.txt?dl=0")!
// the dropbox link above is a shared link so anyone can view it
do {
let content = try String(contentsOf: url)
print("File Content: \(content)")
} catch let error as NSError {
print("\(error)")
}
When I run this code I get this error:
Error Domain=NSCocoaErrorDomain Code=260 "The file “test.txt” couldn’t be opened because there is no such file."(there's more to the error but it's quite big)
Can anyone help me out please? Thanks.
There's more to the error but it's quite big
Do not strip error messages. If you don't know to fix this issue, you probably don't know what to strip to keep it valuable.
How to fix your problem
Select target
Switch to Signing & Capabilities tab
App Sandbox - Network - enable Outgoing Connections (Client)
Change the URL (dl=0) to (dl=1)
0 = display web page with a preview and download link
1 = do not display any web page, just serve the file
let url = URL(string: "https://www.dropbox.com/s/rokwv82h54ogwy1/test.txt?dl=1")!
// Change dl=0 to dl=1 ^
do {
let content = try String(contentsOf: url)
print("File Content: \(content)")
} catch let error as NSError {
print("\(error)")
}
Run again and you'll get:
File Content:
This is a test. If you can read this, you have passed! :)
Do not use String(contentsOf: url), because it's not async and it will block the main thread (UI).
Asynchronous example - imagine you have a view controller with one text field (label) and you'd like to display the file content there:
import Cocoa
class ViewController: NSViewController {
#IBOutlet var textField: NSTextField!
override func viewWillAppear() {
super.viewWillAppear()
textField.stringValue = "Loading ..."
loadRemoteFile()
}
func loadRemoteFile() {
let url = URL(string: "https://www.dropbox.com/s/rokwv82h54ogwy1/test.txt?dl=1")!
let task = URLSession.shared.dataTask(with: url) { data, _, error in
// Following code is not called on the main thread. If we'd like to
// modify UI elements, we have to dispatch our code on the main thread.
// Hence the DispatchQueue.main.async {}.
if let error = error {
print("Failed with error: \(error)")
DispatchQueue.main.async { self.textField.stringValue = "Failed" }
return
}
guard let data = data,
let content = String(data: data, encoding: .utf8) else {
print("Failed to decode data as an UTF-8 string")
DispatchQueue.main.async { self.textField.stringValue = "Failed" }
return
}
print("Content: \(content)")
DispatchQueue.main.async { self.textField.stringValue = content }
}
// At this point, we have a task which will download the file, but the task
// is not running. Every task is initially suspended.
task.resume() // Start the background task
// At this point, your program normally continues, because the download
// is executed in the background (not on the main thread).
}
}

Copying Resource Files For Xcode SPM Tests

I am new to the Swift Package Manager but with its integration into Xcode 11 it is time to give it a try. I have a new application and SPM library within a new workspace. I have a working library with tests and have successfully imported the library into the application.
I need to extend the SPM library with new tests that parse json files. I have learned that a resources directory feature is not supported. The only workable scheme seems to be a file copy step added to the library build process so that the resource files can be discovered by the executable.
I could figure out how to do this from the command line but not with Xcode running the build and test. There is no Copy Bundle Resources, build phase for swift packages. In fact everything appears to be hidden by Xcode.
I have looked within the SPM for Makefile type files that would allow me to edit default command line actions thereby circumventing Xcode; but I am not seeing them.
Is there some way to interact/control how Xcode 11 builds SPM targets so that I can copy non-code files to test targets?
Got it working!!!
struct Resource {
let name: String
let type: String
let url: URL
init(name: String, type: String, sourceFile: StaticString = #file) throws {
self.name = name
self.type = type
// The following assumes that your test source files are all in the same directory, and the resources are one directory down and over
// <Some folder>
// - Resources
// - <resource files>
// - <Some test source folder>
// - <test case files>
let testCaseURL = URL(fileURLWithPath: "\(sourceFile)", isDirectory: false)
let testsFolderURL = testCaseURL.deletingLastPathComponent()
let resourcesFolderURL = testsFolderURL.deletingLastPathComponent().appendingPathComponent("Resources", isDirectory: true)
self.url = resourcesFolderURL.appendingPathComponent("\(name).\(type)", isDirectory: false)
}
}
Usage:
final class SPMTestDataTests: XCTestCase {
func testExample() throws {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct
// results.
XCTAssertEqual(SPMTestData().text, "Hello, World!")
let file = try Resource(name: "image", type: "png")
let image = UIImage(contentsOfFile: file.url.path)
print(image)
}
}
I found the key of using #file here
This is another workaround to provide access to test resources. Hopefully an answer to the OP's question will be forthcoming.
Using the code below, an extension is created to allow callers to create URL's to test resources like this.
let url = URL(forResource: "payload", type: "json")
This code requires that all resource files be located in a flat directory named "Resources" just under the test target.
// MARK: - ./Resources/ Workaround
// URL of the directory containing non-code, test resource fi;es.
//
// It is required that a directory named "Resources" be contained immediately below the test target.
// Root
// Package.swift
// Tests
// (target)
// Resources
//
fileprivate let _resources: URL = {
func packageRoot(of file: String) -> URL? {
func isPackageRoot(_ url: URL) -> Bool {
let filename = url.appendingPathComponent("Package.swift", isDirectory: false)
return FileManager.default.fileExists(atPath: filename.path)
}
var url = URL(fileURLWithPath: file, isDirectory: false)
repeat {
url = url.deletingLastPathComponent()
if url.pathComponents.count <= 1 {
return nil
}
} while !isPackageRoot(url)
return url
}
guard let root = packageRoot(of: #file) else {
fatalError("\(#file) must be contained in a Swift Package Manager project.")
}
let fileComponents = URL(fileURLWithPath: #file, isDirectory: false).pathComponents
let rootComponenets = root.pathComponents
let trailingComponents = Array(fileComponents.dropFirst(rootComponenets.count))
let resourceComponents = rootComponenets + trailingComponents[0...1] + ["Resources"]
return URL(fileURLWithPath: resourceComponents.joined(separator: "/"), isDirectory: true)
}()
extension URL {
init(forResource name: String, type: String) {
let url = _resources.appendingPathComponent("\(name).\(type)", isDirectory: false)
self = url
}
}

How do you save a .txt file using Swift 3 on a USB if it is connected using filemanager? (macOS app development)

This may be building upon the question mentioned:
Read and write data from text file
What I am trying to do, is if a certain type of removable volume is connected, have the option of writing the file directly on the drive, and if possible in a certain subfolder in that drive.
Here is the code I am currently using:
let fileManager = FileManager.default
var Extension: String = "txt"
let FileName: String = "TestFileName"
var DocumentDirURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let fileURL = DocumentDirURL.appendingPathComponent(FileName).appendingPathExtension(Extension)
print("FilePath: \(fileURL.path)")
let DocumentIntro = "This text will be on the text file\n"
do {
// Write to the file
try DocumentIntro.write(to: fileURL, atomically: true, encoding: String.Encoding.utf8)
} catch let error as NSError {
print("Failed writing to URL: \(fileURL), Error: " + error.localizedDescription)
}
var FileContents = "" // Used to store the file contents
do {
// Read the file contents
FileContents = try String(contentsOf: fileURL)
} catch let error as NSError
print("Failed reading from URL: \(fileURL), Error: " + error.localizedDescription)
}
The only way I'm aware of to detect removable drives is to use the Disk Arbitration Framework.
The good news is that I did write some minimal documentation for it back when I was at Apple.
The bad news is that it's a pure C framework (Core Foundation), which makes it really unpleasant to use even in Objective-C, much less Swift.

Move Document/Inbox file to another location - Xcode 7, Swift 2

I have a file that is in my Documents/Inbox and is shown in my Print log:
File:
file:///private/var/mobile/Containers/Data/Application/5388031B-48B5-48D6-8299-B3FEDC1D7F45/Documents/Inbox/Pizza-6.pdf
I looked here and saw a way to delete files, but I want to move them out of the Inbox folder to another folder I want to create. How would I do this? I can't find anything for iOS and Swift 2. Thank you.
Here is what I ended up doing:
// MOVING AND SAVING INCOMING PDF TO FILE MANAGER FROM INBOX
let filemgr = NSFileManager.defaultManager()
let docsDirURL = try! filemgr.URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: true)
// Create a new folder in the directory named "Recipes"
print("Creating new folder...")
let documentsPath = NSURL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0])
let newPath = documentsPath.URLByAppendingPathComponent("Recipes")
do {
try NSFileManager.defaultManager().createDirectoryAtPath(newPath.path!, withIntermediateDirectories: true, attributes: nil)
} catch let error as NSError {
NSLog("Unable to create directory \(error.debugDescription)")
}
// Then check if the Recipes directory exists. If not, create it
let recipesURL = docsDirURL.URLByAppendingPathComponent("Recipes")
if !filemgr.fileExistsAtPath(docsDirURL.path!) {
do {
try filemgr.createDirectoryAtURL(recipesURL, withIntermediateDirectories: true, attributes: nil)
print("Directory created at: \(recipesURL)")
} catch let error as NSError {
NSLog("Unable to create directory \(error.debugDescription)")
return
}
}
// Move file from Inbox to Recipes Folder
let incomingFileName = incomingFileTransfer.lastPathComponent!
let startingURL = incomingFileTransfer
let savePDFURL = recipesURL.URLByAppendingPathComponent(incomingFileName)
if !filemgr.fileExistsAtPath(savePDFURL.path!) {
do {
try filemgr.moveItemAtURL(startingURL, toURL: savePDFURL)
} catch let error as NSError {
NSLog("Unable to move file \(error.debugDescription)")
}
}

Swift 2 - fileURLWithPath returns NIL

I am trying to load a video using MPMoviePlayerController. I have added the file in my project and also added to the Build Phase "Copy Bundle Resource".
When I call the function to open the video I can see that the path its printed out however, the app crashes on 'fileURLWithPath`.
What am I doing wrong? Are .mp4 flies playable?
func firstView(){
if let filePath = NSBundle.mainBundle().pathForResource("firstVideo", ofType: "mp4") {
print(filePath)
//PRINTS: /var/mobile/Containers/Bundle/Application/B93BB049-DA87-4D89-AEBE-A5C92C01726E/MyApp.app/firstVideo.mp4
firstMoviePlayer!.contentURL = NSURL.fileURLWithPath(filePath) // fatal error
}
firstMoviePlayer!.repeatMode = .None
firstMoviePlayer!.controlStyle = MPMovieControlStyle.Embedded
firstMoviePlayer!.view.frame = FirstTimeVideoView.frame
FirstTimeVideoView.addSubview(firstMoviePlayer!.view)
firstMoviePlayer!.play()
}
You just need to change:
if let filePath = NSBundle.mainBundle().pathForResource("firstVideo", ofType: "mp4") {
print(filePath)
//PRINTS: /var/mobile/Containers/Bundle/Application/B93BB049-DA87-4D89-AEBE-A5C92C01726E/MyApp.app/firstVideo.mp4
firstMoviePlayer!.contentURL = NSURL.fileURLWithPath(filePath) // fatal error
}
TO:
if let filePath = NSBundle.mainBundle().URLForResource("firstVideo", ofType: "mp4") {
print(filePath)
//PRINTS: /var/mobile/Containers/Bundle/Application/B93BB049-DA87-4D89-AEBE-A5C92C01726E/MyApp.app/firstVideo.mp4
firstMoviePlayer!.contentURL = NSURL.fileURLWithPath(filePath) // fatal error
}

Resources