SwiftUI getting data from Data(contentsOf: file) - swift

I'm trying to get some content from a .txt file by doing
let ruterQuery = loadR(filename: "content.txt")
func loadR( filename: String ) -> String {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
print(data)
return "done"
}
But when I print the data it only says "2045 bytes", how do I get the actual content of the file?
This is the content.txt, it's for an api url as a "query" of what fields to be returned. So the url will be "https://api.myapi.com/planner?query=" + content.txt
"{\n\tplaces(\n\t\tids:[\"ABC:StopPlace:8329\", \"ABC:StopPlace:0808\"]\n\t) {\n\t\tname\n\t\tid\n\t\testimatedCalls(timeRange: 72100, numberOfDepartures: 20) {\n\n\t\t\trealtime\n\t\t\trealtimeState\n\t\t\texpectedDepartureTime\n\t\t\tpredictionInaccurate\n\t\t\tdestinationDisplay {\n\t\t\t\tfrontText\n\t\t\t}\n\t\t\tquay {\n\t\t\t\tid\n\t\t\t}\n\n\t\t\tsituations {\n\t\t\t\tid\n\t\t\t\tlines {\n\t\t\t\t\tid\n\t\t\t\t}\n\t\t\t\tsummary {\n\t\t\t\t\tvalue\n\t\t\t\t\tlanguage\n\t\t\t\t}\n\t\t\t\tstopPlaces {\n\t\t\t\t\tid\n\t\t\t\t}\n\t\t\t\tdescription {\n\t\t\t\t\tvalue\n\t\t\t\t\tlanguage\n\t\t\t\t}\n\t\t\t\tvalidityPeriod {\n\t\t\t\t\tstartTime\n\t\t\t\t\tendTime\n\t\t\t\t}\n\t\t\t\tseverity\n\t\t\t\tsituationNumber\n\t\t\t}\n\n\t\t\tserviceJourney {\n\t\t\t\tid\n\t\t\t\tpublicCode\n\t\t\t\tprivateCode\n\t\t\t\tserviceAlteration\n\t\t\t\ttransportSubmode\n\t\t\t\tdirectionType\n\n\t\t\t\tnotices {\n\t\t\t\t\tid\n\t\t\t\t\ttext\n\t\t\t\t\tpublicCode\n\t\t\t\t}\n\n\t\t\t\tjourneyPattern {\n\t\t\t\t\tid\n\t\t\t\t\tline{\n\t\t\t\t\t\tid\n\t\t\t\t\t\tname\n\t\t\t\t\t\tpublicCode\n\t\t\t\t\t\ttransportMode\n\t\t\t\t\t\ttransportSubmode\n\t\t\t\t\t\tpresentation {\n\t\t\t\t\t\t\ttextColour\n\t\t\t\t\t\t\tcolour\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsituations {\n\t\t\t\t\t\t\tid\n\t\t\t\t\t\t\tdescription {\n\t\t\t\t\t\t\t\tvalue\n\t\t\t\t\t\t\t\tlanguage\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tvalidityPeriod {\n\t\t\t\t\t\t\t\tstartTime\n\t\t\t\t\t\t\t\tendTime\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tsituations {\n\t\t\t\t\tid\n\t\t\t\t\tlines {\n\t\t\t\t\t\tid\n\t\t\t\t\t}\n\t\t\t\t\tsummary {\n\t\t\t\t\t\tvalue\n\t\t\t\t\t\tlanguage\n\t\t\t\t\t}\n\t\t\t\t\tstopPlaces {\n\t\t\t\t\t\tid\n\t\t\t\t\t}\n\t\t\t\t\tdescription {\n\t\t\t\t\t\tvalue\n\t\t\t\t\t\tlanguage\n\t\t\t\t\t}\n\t\t\t\t\tvalidityPeriod {\n\t\t\t\t\t\tstartTime\n\t\t\t\t\t\tendTime\n\t\t\t\t\t}\n\t\t\t\t\tseverity\n\t\t\t\t\tsituationNumber\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\t}\n}\n"

The content of the file is JSON rather than simply text.
You get the the string representation with
func loadR(filename: String ) -> String {
let url = Bundle.main.url(forResource: filename, withExtension: "txt")!
let data = try! Data(contentsOf: url)
let string = String(data: data, encoding: .utf8)!
print(string)
return string
}
If the code crashes you made a design mistake. As files in the bundle are immutable at runtime the code must not crash.
Another bad practice are all the unnecessary whitespace characters. Unlike a human being the device doesn't care about prettyPrinting

Related

uncaught exemption when writing to a static dictionary in an HTTP request

I'm getting an Uncaught exemption within a http closure related to a dictionary stating there's an uncaught exemption. When I set a breakpoint exemptions, it points to a dictionary. The dictionary in question is declared in a struct as a static var and has multiple values already in it so how can this be happening? Here's the http request.
session.dataTask(with: request){ (data, response, error) in
if let data = data,
let tile = UIImage(data: data),
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first{
let fileName = Date().timeIntervalSince1970
let filePath = documentsURL.appendingPathComponent(String(describing: fileName))
Maps.tileCachePath[url] = fileName //<- this is where the exception happens
//make sure there is no old file and if so delete it
if FileManager.default.fileExists(atPath: filePath.path){
do {
try FileManager.default.removeItem(at: filePath)
} catch{
print("error deleting old tile")
}
}
//now write the new file
FileManager.default.createFile(atPath: filePath.path, contents: data, attributes: nil)
print(filePath.path)
//return
result(tile, error)
} else {
result(nil, error)
}
}.resume()
It's a typo
Replace
Maps.tileCachePath[url] = fileName
with
Maps.tileCachePath[url] = filePath
Basically Date().timeIntervalSince1970 as a filename is a very bad idea. The number contains fractional seconds which are treated as a file extension.
Use a more reliable file name like a formatted date or at least remove the fractional seconds and add a real file extension.
Date().timeIntervalSince1970
is a double, you might need a string value there.

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)
})

data from base64 url

I have a URL in the form of
foo://?data:application/x-foo;base64,OjAyMDAwMDA0MDAwMEZBDQo6MTAwMDA...
and now need to extract the base64 data into a Data object.
Unfortunately it seems the Data object does not support this yet as
let data = try Data(contentsOf: url)
returns NSURLConnection finished with error - code -1002 when trying.
While I could decode the URL manually I am wondering if I am missing a simple standard way of doing this. How would you do this?
Actually you can decode Base64 data from an URL (see for
example Base64 Decoding in iOS 7+ where this is demonstrated in Objective-C). The format is a bit different from what
you have:
let url = URL(string: "data:application/octet-stream;base64,SGVsbG8gd29ybGQh")!
let data = try! Data(contentsOf: url)
print(String(data: data, encoding: .utf8)!) // Hello world!
(Error checking omitted for brevity.)
You have to separate the base64 encoded part of the URL from the other parts, decode it, then join the original non-encoded part with the decoded part and get the data from there.
extension URL {
init?(partialBase64Url: String){
guard let base64part = base64Url.components(separatedBy: "base64,").last, let base64Data = Data(base64Encoded: base64part), let decodedString = String(data: base64Data, encoding: .utf8) else {
return nil
}
let decodedUrl = base64Url.components(separatedBy: "base64,").dropLast().joined() + decodedString
self.init(string: decodedUrl)
}
}
let decodedUrl = URL(partialBase64Url: "foo://?data:application/x-foo;base64,dGVzdFVybFN0cmluZw==")
Value of decodedUrl: "foo://?data:application/x-foo;testUrlString", as expected, since dGVzdFVybFN0cmluZw== is the base64 encoded value of testUrlString.

Content Blocker extension with a String instead of a file

I'm using the function NSItemProvider(contentsOfURL: NSBundle.mainBundle().URLForResource("blockerList", withExtension: "json") in a content blocker extension.
The thing is that all my rules are stored in a few dictionaries and, when I'm using this function, it's always because the rules have changed. I'm currently creating a String from these dictionaries that looks like "[{\"trigger\": {\"url-filter\": \"webiste.com\"},\"action\": {"\type\": \"css-display-none\",\"selector\":\".testContentBlocker\"}}]"and I have to transform it in a JSON file to finally be able to use it in the function written previously described.
Instead of having to put the String in a JSON file to be able to use it, could I do something simpler to use NSItemProvider()?
By loading the extension up in the debugger (and by using Hopper), you can see that NSItemProvider(contentsOfURL:) is simply registering to provide data from the file's contents with type public.json.
(lldb) po attachment
<NSItemProvider: 0x7fd4c250f2a0> {types = (
"public.file-url",
"public.json"
)}
It's roughly equivalent to this:
// possible implementation of NSItemProvider.init(contentsOfURL:)
convenience init?(contentsOfURL fileURL: NSURL!)
{
self.init(item: fileURL, typeIdentifier: (fileURL.fileURL ? kUTTypeFileURL : kUTTypeURL) as String)
let type = UTTypeCreatePreferredIdentifierForTag(
kUTTagClassFilenameExtension, fileURL.pathExtension!, nil)?.takeRetainedValue() as! String
registerItemForTypeIdentifier(type) { completionHandler, expectedValueClass, options in
let data = try! NSData(contentsOfURL: fileURL, options: .DataReadingMappedAlways)
completionHandler(data, nil)
}
}
So you can do this yourself, in memory:
// get the data
let data = NSData(contentsOfURL: NSBundle.mainBundle().URLForResource("blockerList", withExtension: "json")!)
// put the data in an item provider
let attachment = NSItemProvider(item: data, typeIdentifier: kUTTypeJSON as String)
// send the item to Safari
let item = NSExtensionItem()
item.attachments = [attachment]
context.completeRequestReturningItems([item], completionHandler: nil);
If you want to provide content dynamically, you can use NSJSONSerialization to transform a dictionary into NSData at runtime.

Simple way to read local file using Swift?

I'm trying to learn the new Swift programming language. It looks great, but I'm having a difficult time doing something as simple as reading the content of a local .txt file.
I have tried the few examples I could find through Google, but they give compile errors, like this answer here: Read and write data from text file
If I tweak the code a bit, it works, but can only read from a special location within the project.
Why isn't it just as simple to read a .txt file with Swift as it is with for instance Ruby? And how would I go about reading the content of a file located at ~/file.txt?
Thnx
If you have a tilde in your path you can try this:
let location = "~/file.txt".stringByExpandingTildeInPath
let fileContent = NSString(contentsOfFile: location, encoding: NSUTF8StringEncoding, error: nil)
otherwise just use this:
let location = "/Users/you/Desktop/test.txt"
let fileContent = NSString(contentsOfFile: location, encoding: NSUTF8StringEncoding, error: nil)
This gives you a string representation of the file, which I assumed is what you want.
You can use NSData(contentsOfFile: location) to get a binary representation, but you would normally do that for, say, music files and not a text file.
Update: With Xcode 7 and Swift 2 this doesn't work anymore. You can now use
let location = NSString(string:"~/file.txt").stringByExpandingTildeInPath
let fileContent = try? NSString(contentsOfFile: location, encoding: NSUTF8StringEncoding)
let file = "/Users/user/Documents/text.txt"
let path=URL(fileURLWithPath: file)
let text=try? String(contentsOf: path)
This would work:
let path = "~/file.txt"
let expandedPath = path.stringByExpandingTildeInPath
let data: NSData? = NSData(contentsOfFile: expandedPath)
if let fileData = data {
let content = NSString(data: fileData, encoding:NSUTF8StringEncoding) as String
}
Note that data may be nil, so you should check for that.
EDIT:
Don't forget conditional unwrapping - looks much nicer ;)
Relative path tip:
Instead of doing this:
NSString("~/file.txt").stringByExpandingTildeInPath
You can do this:
"\(NSHomeDirectory())/file.txt"
You may find this tool useful to not only read from file in Swift but also parse it simultaneously: https://github.com/shoumikhin/StreamScanner
Just specify the file path and data delimiters like this (see readme for more options):
import StreamScanner
if let input = NSFileHandle(forReadingAtPath: "/file/path")
{
let scanner = StreamScanner(source: input, delimiters: NSCharacterSet(charactersInString: ":\n")) //separate data by colons and newlines
while let field: String = scanner.read()
{
//use field
}
}
Hope, this helps.
Using the answer by Atomix, this will work in Swift 4:
let location = NSString(string: "~/test.txt").expandingTildeInPath
let fileContent = try? NSString(contentsOfFile: location, encoding: String.Encoding.utf8.rawValue)
This worked for me in Swift 2.1, XCode7 to get the location and print the contents of CSV. ( you can create a simple CSV in Text Wrangler)
override func viewDidLoad() {
super.viewDidLoad()
let location = NSString(string:"/Users/*Myusername*/Documents/myCSVfile.csv")
let fileContent = try? NSString(contentsOfFile: location as String, encoding: NSUTF8StringEncoding)
print(fileContent)
}
Swift 4:
let filePath = "/Users/UserName/Desktop/FolderName/FileName.txt"
let fullPath = NSString(string: filePath).expandingTildeInPath
do
{
let fileContent = try NSString(contentsOfFile: fullPath, encoding: String.Encoding.utf8.rawValue)
print(fileContent)
}
catch
{
print(error)
}
filename doesn't need to have scheme like file://, and can be relative like ../docs/test.txt.
Remember to catch any error thrown, or rethrow.
let url = URL(fileURLWithPath: filename)
let contents = try String(contentsOf: url, encoding: .utf8)