I cant read my text files from my application's Bundle - swift

I used to read the text files from my application's bundle by using the following code. However, no matter what my application can't find them anymore. I am 100% sure that all my files are in the Assets.xcassets, I can see them, edit them, transform them from a directory to another. But my application doesn't want to read them, please tell me what I missed!!
this is the procedure I am using...
func readBundle(file:String) -> String
{
var res: String = ""
if let path = NSBundle.mainBundle().pathForResource(file, ofType: "txt")
{
let fm = NSFileManager()
let exists = fm.fileExistsAtPath(path)
if(exists)
{
let c = fm.contentsAtPath(path)
res = NSString(data: c!, encoding: NSUTF8StringEncoding) as! String
}
}
return res
}
I am using it like this:
let res = readBundle("test")
print(res)

when storing non image files in XCAssets, you should use NSDataAsset to acccess their content
https://developer.apple.com/library/ios/documentation/UIKit/Reference/NSDataAsset_Class/
func readBundle(file:String) -> String
{
var res = ""
if let asset = NSDataAsset(name: file) ,
string = String(data:asset.data, encoding: NSUTF8StringEncoding){
res = string
}
return res
}

In the another option then 'XCAssets' you can create a separate folder/group of your resources other than images in the project structure, check if they exist in the Copy Bundle Resource in the Build phases section of your project's main target
If you add resource like this your current code should work as it is
func readBundle(file:String) -> String
{
var res: String = ""
if let path = NSBundle.mainBundle().pathForResource(file, ofType: "txt")
{
//you should be able to get the path
//other code as you has written in the question
}
return res
}

Related

Path part of "filename.m4v" is "/private/tmp", which is unexpected

I'm writing some Swift code to parse filenames of video files and extract the show, season and episode. These are returned as key/value parts in a dictionary. As part of my unit tests, I found something odd. First the code (comments and whitespace removed):
public static func parse(_ filename: String, defaults: [String: String] = [String: String]()) -> [String: String] {
let url = URL(fileURLWithPath: filename)
let file = url.deletingPathExtension().lastPathComponent
if file.count == 0 {
return ret
}
if file.count > 0 {
ret["file"] = file
}
let ext = url.pathExtension
if ext.count > 0 {
ret["extension"] = ext
}
let path = url.deletingLastPathComponent().path
if path.count > 0 {
ret["path"] = path
}
I called this in my test case thus...
ParseVideoFilename.parse("non-empty-filename.m4v")
And this is what resulted:
["ext": "m4v", "file": "non-empty-filename", "path": "/private/tmp"]
I am a bit surprised about that path. I did not pass that in, so I assume URL is doing something here. I don't expand the path nor resolve it. Is this expected behavior, and if so, why?
Running your code in a playground just gave me file:///private/var/folders/1p/wpwdypm96_s5zfwxxzvwwp0m0000gn/T/com.apple.dt.Xcode.pg/containers/com.apple.dt.playground.stub.iOS_Simulator.Playground-C18F0418-5C1D-4772-9AE9-E3EF9AA2F07C/non-empty-filename.m4v as the output of
let url = URL(fileURLWithPath: filename)
print(url.absoluteString)
This looks to me as the current directory of the process that gets executed by Playground. I'm not at all surprised by this, since we gave URL a relative path without any base and the file URI scheme doesn't really know how to handle relative paths (see e.g. this answer)
If you absolutely don't want to see these artifacts, you can modify your parser to use an absolute path:
let url = URL(fileURLWithPath: "/" + filename)
print(url.absoluteString) // prints "file:///non-empty-filename.m4v"

Swift: unzipping file

I’m trying to get String from txt file inside the zip file using native libcompression library. Actually I use the code from
https://github.com/mw99/DataCompression/blob/master/Sources/DataCompression.swift.
At first, I was doing:
let zip = try? Data(contentsOf: "/.../test.zip")
let tmp: Data? = zip?.unzip()
let txt: String? = String(data: tmp!, encoding: .utf8)
But how do I get the contents of zip file and how do I get data from certain txt file?
ZIP Foundation supports accessing individual entries in ZIP archives.
You have to initialize an archive by passing a file URL to the Archive initializer.
Afterwards you can access a specific entry via subscripting:
let fileManager = FileManager()
let currentWorkingPath = fileManager.currentDirectoryPath
var archiveURL = URL(fileURLWithPath: currentWorkingPath)
archiveURL.appendPathComponent("test.zip")
guard let archive = Archive(url: archiveURL, accessMode: .read) else {
return
}
guard let entry = archive["file.txt"] else {
return
}
var destinationURL = URL(fileURLWithPath: currentWorkingPath)
destinationURL.appendPathComponent("out.txt")
do {
try archive.extract(entry, to: destinationURL)
} catch {
print("Extracting entry from archive failed with error:\(error)")
}
You can also directly access the contents of entry by using the closure based API. This allows you to process the entry without writing it to the file system first:
try archive.extract(entry, consumer: { (data) in
print(data.count)
})

XCTest: Load file from disk without Bundle on all platforms (Xcode, SPM Mac/Linux)

I am looking for a way to load files from disk within an XCTestCase that does not depend on Bundle.
Bundle works well when running the tests from Xcode (or with xcodebuild on the terminal), but bundles are part of the Xcode project and not available to Swift Package Manager (when running swift test), neither on the Mac nor in Linux.
Is there a way to specify the current directory where tests should be run that works in all platforms? Or maybe there is a way to determine where the tests are located that also works on all platforms?
FileManager.default.currentDirectoryPath only returns the current execution path (working directory).
This method seems to work for me, and should work with both Xcode 11 and from the command line. Copy of the answer I just wrote here.
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
Seems like when running tests with Swift Package Manager (swift test), the working directory is the root of the project. This allows for easily loading resources from disk using a relative path (eg. ./Tests/Resources).
After evaluating different options, I wrote the following class to retrieve the path both from the test bundle, if available, or from a given path.
class Resource {
static var resourcePath = "./Tests/Resources"
let name: String
let type: String
init(name: String, type: String) {
self.name = name
self.type = type
}
var path: String {
guard let path: String = Bundle(for: Swift.type(of: self)).path(forResource: name, ofType: type) else {
let filename: String = type.isEmpty ? name : "\(name).\(type)"
return "\(Resource.resourcePath)/\(filename)"
}
return path
}
}
Resources must be added to all test targets for them to be available in the bundle. The above class could be updated to support multiple resource paths, by file extension, for instance.
Resources can be then loaded from an XCTest as needed:
let file = Resource(name: "datafile", type: "csv")
let content = try String(contentsOfFile: file)
let image = try UIImage(contentsOfFile: Resource(name: "image", type: "png"))
Extensions can be added to Resource to load the contents in different formats.
extension Resource {
var content: String? {
return try? String(contentsOfFile: path).trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
}
var base64EncodedData: Data? {
guard let string = content, let data = Data(base64Encoded: string) else {
return nil
}
return data
}
var image: Image? {
return try? UIImage(contentsOfFile: path)
}
}
And be used as:
let json = Resource(name: "datafile", type: "json").contents
let image = Resource(name: "image", type: "jpeg").image

Get path of a file in a data set located in Assets.xcassets

I have a data set of audio files in my Assets.xcassets:
I'm trying to get the path of one of those audio files like this:
let path: String = Bundle.main.path(forResource: "acoustic_grand_piano/A4", ofType: "f32")!
But I get a EXC_BAD_INSTRUCTION. I tried to look on the internet but I don't find anything on Data Sets.
How can I get the content of one of these files?
Thanks!
Try this:
Manually put your files into a folder, named anything you want.
Append ".bundle" to the folder to create a bundle. You'll get a warning, accept it. Congrats, you've just created your first bundle! :-)
Manually drag that folder into your app.
Get at your files by using the following code....
public func returnFile(_ named:String) -> String {
let path: String = Bundle.main.path(forResource: "myAudioFiles", ofType: "bundle")! + "/" + name + ".f32"
do {
return try String(contentsOfFile: path)
}
catch let error as NSError {
return error.description
}
}
Now, my files are text files of CIKernel code. Since your's are audio files you may need to change the String return to something else.
EDIT:
In my case I'm using a framework, as I wish to share these files/images with extensions and other apps. If you are working in such a set up, here's the unaltered code:
public func returnFile(_ resource:String, _ fileName:String, _ fileType:String) -> String {
let identifier = "com.companyname.appname" // replace with framework bundle identifier
let fileBundle = Bundle.init(identifier: identifier)
let filePath = (fileBundle?.path(forResource: resource, ofType: "bundle"))! + "/" + fileName + "." + fileType
do {
return try String(contentsOfFile: filePath)
}
catch let error as NSError {
return error.description
}
}

fileExistsAtPath check for filename?

How to check whether there is a file in a directory with only the name without extension? Now the files are written in my directory, their name will be generated from the id file. Accordingly, when I'm looking for a file, let file = "\ (fileId) .pdf", in the directory it is, but if no extension, it will not be found. Either return as easier extension from the server?
public var isDownloaded: Bool {
let path = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as String
let url = NSURL(fileURLWithPath: path)
let filePath = url.URLByAppendingPathComponent("\(fileMessageModel.attachment.id)")!.path!
let fileManager = NSFileManager.defaultManager()
return fileManager.fileExistsAtPath(filePath)
}
enumeratorAtPath creates a deep enumerator -- i.e. it will scan contents of subfolders and their subfolders too. For a shallow search, user contentOfDirectortAtPath:
func file(fileName: String, existsAt path: String) -> Bool {
var isFound = false
if let pathContents = try? NSFileManager.defaultManager().contentsOfDirectoryAtPath(path) {
pathContents.forEach { file in
if (file as NSString).stringByDeletingPathExtension == fileName {
isFound = true
return
}
}
}
return isFound
}
if let path = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first {
if file("something", existsAt: path) {
// The file exists, do something about it
}
}
What about iterating over the files in the directory and testing the name with extension excluded?
let filemanager:FileManager = FileManager()
let files = filemanager.enumeratorAtPath(/* your directory path */)
while let file = files?.nextObject() {
// Remove file name extension
// Do file name comparison here
}
In terms of time complexity is will be O(n), however, as long as there are not too many files, you are good to go. On the other hand, if there are many files, you will need to consider a more efficient way to traverse, may be a trie data structure consisted of all file names in that directory.