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.
Related
I'm currently trying to store some files on my iOS device. The contents of the file are encrypted, but I was wondering if I can append some kind of integrity check to the file as well, preferably using the FileAttributeKey.
I tried the following, which doesn't work
extension FileAttributeKey {
static let integrity = FileAttributeKey("NSFileIntegrity")
}
let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let fileName = "test"
let filePath = docs.appendingPathComponent(fileName).path
defer {
try! FileManager.default.removeItem(atPath: filePath)
}
let data = Data("Hello world".utf8)
// This line fails too
// FileManager.default.createFile(atPath: filePath, contents: data, attributes: [.integrity: "SHA256"])
FileManager.default.createFile(atPath: filePath, contents: data, attributes: [:])
do {
try FileManager.default.setAttributes([.integrity: "SHA256"], ofItemAtPath: filePath)
} catch {
print(error)
}
print(try FileManager.default.attributesOfItem(atPath: filePath))
So the questions are:
Is there a way to create and append a custom FileAttributeKey to a file.
Is there a (better) way to add integrity checks to a file?
If you use authenticated encryption then you get integrity checks for free. Every time you decrypt, integrity will be checked for you and in case of errors the decryption will fail. Just use a mode like GCM or OCB and you are done.
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
I am attempting to store multiple lines of text in a local text file on an iphone. I have code which will create a text document, write data to that document and read data from this document.
However, if i try and add more text to this document, it will only store the most recent line of text which has been added.
The code I have for creating, writing and reading text from this document is as follows:
//Storing user rewards
let fileName = "Rewards"
let DocumentDirURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let fileURL = DocumentDirURL.appendingPathComponent(fileName).appendingPathExtension("txt")
//print("File Path: \(fileURL.path)")
let writeString = rewardLBL.text
do {
//writing to the file
try writeString?.write(to: fileURL, atomically: true, encoding: String.Encoding.utf8)
} catch let error as NSError{
print("failed to write")
print(error)
}
var readString = ""
do {
readString = try String(contentsOf: fileURL)
}catch let error as NSError{
print("failed to readFile")
print(error)
}
print(readString)
I need this to allow for multiple entries of text to be stored, rather than just the most recent data which was written.
I suspect that due to the code being inside the 'viewDidLoadi()' method that it is constantly recreating the same document and thus always making a new version which overwrites the old Rewards.txt document.
Any help is greatly appreciated, thanks.
Since you are using write, it will overwrite whatever is written earlier.
try writeString?.write(to: fileURL, atomically: true, encoding:
String.Encoding.utf8)
You need to append line of text to your file, which will not overwrite previous written lines. Something like this:
writeString.data(using: String.Encoding.utf8)?.write(to: fileURL, options: Data.WritingOptions.withoutOverwriting)
I have a video file on S3 that I am trying to save to disk. However, if the file already exists on disk, I want to overwrite it. I wrote this function to download the file but it never saves the file. I can see the progress % increasing. But, how do I access the resulting file and save it to disk?
var finalPath: NSURL?
Alamofire.download(.GET, s3Url) { temporaryURL, response in
let fileManager = NSFileManager.defaultManager()
if let directoryURL = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0] as? NSURL {
let pathComponent = response.suggestedFilename
finalPath = directoryURL.URLByAppendingPathComponent(pathComponent!)
println(finalPath)
//remove the file if it exists
if fileManager.fileExistsAtPath(finalPath!.absoluteString!) {
println("file exists on disk, removing..")
fileManager.removeItemAtPath(finalPath!.absoluteString!, error: nil)
}
return finalPath!
}
return temporaryURL
}
.validate()
.progress { bytesRead, totalBytesRead, totalBytesExpectedToRead in
let progress = (Double(totalBytesRead) / Double(totalBytesExpectedToRead)) * 100
println(String(format: "%.2f", progress))
}
.response { request, response, data, error in
println(request)
println(response)
if let mediaData = data {
println("saving file to disk")
mediaData.writeToURL(finalPath!, atomically: true)
}
}
Normally I would use the example provided in the docs, but It fails if the file already exists. ie:
let destination = Alamofire.Request.suggestedDownloadDestination(directory: .DocumentDirectory, domain: .UserDomainMask)
So, how can I download the file, overwrite it if it exists and record the path that the file is written to to my coreData database?
You need to delete the file first. Alamofire only tries to move the file from the temp location to the final location that you provide in the destination closure.
You can create an extension on Alamofire.DownloadRequest to provide options for how to download the file where you can use the option DownloadRequest.DownloadOptions.removePreviousFile.
for details on how to do that see my answer to this question.
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)