How can I write to an xml document in Swift? I have this code:
let content = "<tag>Example</tag>"
var error: NSError?
content.writeToFile(NSBundle.mainBundle().pathForResource("my_file", ofType: "xml")!, atomically: true, encoding: NSUTF8StringEncoding, error: &error)
But this does not update the file in my Xcode project or on the device. How can I get this to work?
To collect all the fragments provided in comments into one place:
The app bundle is read-only on iOS. You can't save to it. It isn't forced to be read-only on Mac OS, but you should still treat it as such. As a result of this difference, code that modifies the app bundle may work on the simulator, but fail on an actual device.
You should save your contents to a file in the user's documents directory or one of the other sandbox directories. That code might look like this:
let docsPath = NSSearchPathForDirectoriesInDomains(
NSSearchPathDirectory.DocumentDirectory,
NSSearchPathDomainMask.UserDomainMask,
true)[0] as! NSString
let filePath = docsPath.stringByAppendingPathComponent("myfile")
let content = "<tag>Example</tag>"
var error: NSError?
content.writeToFile(filePath,
ofType: "xml",
atomically: true,
encoding: NSUTF8StringEncoding,
error: &error)
Then you should add error-checking that checks the bool result of the writeToFile command. If the result is false, check the error to see what went wrong.
Note that there is built-in support in iOS for reading XML, and quite a few third party libraries for saving collections of objects in XML format. You should take a look at NSXMLParser for reading. I can't really recommend one of the XML writing libraries over the others.
If you aren't wedded to XML, you might take a look at JSON. It's more compact, easier to write, easier to read, less memory-intensive, and there is built-in system support for both reading and writing Cocoa data structures from/to JSON files.
Related
Background
I've written one particular app via Windows Forms (C#), Android (Java and Kotlin), HTML5 Web App, ElectronJS (runs on Linux, Mac, and Win10) and even as a UWP (Universal Windows Platform) app.
All Use JSON File For Data
All of those apps use the exact same JSON formatted data for user settings.
That means I can share data on all platforms via the same file and file format.
On Android there is the additional benefit of having the file saved in the UserPrefs (which provides security and backup for user).
The Problem
I've also written the app as an iPhone/iPad app (Swift), however I cannot find the proper way to handle the JSON file storage.
The problem is not related to de-serializing the JSON into my business object. That all works fine. However, I am not sure about:
where should files be stored in the iPhone/iPad system?
can you save a file in some sort of user preference or appdata
location?
How do you open a file for read/write and read/write data? (Swift)
How can I better understand the paths available to read and write
files?
I've searched all over looking for this answer. Can you point me to official documentation, a book, a StackOverflow item or something that explains this clearly? (Hopefully with Swift examples.)
See iOS Storage Best Practices video and the File System Basics document. That should get you going.
In short, app data is generally stored in “application support directory”, documents exposed to the user (e.g. the Files app) are stored in “documents” folder, downloads that can be easily re-retrieved are stored in “caches” folder. Technically you could use UserDefaults for storing of this sort of application data, but it really is not intended for this purpose.
Re opening a file for “read/write”, when dealing with JSON, you don’t generally do that. You read the file into a Data and deserialize the JSON into your model objects.
do {
let fileURL = try FileManager.default
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("appdata.json")
let data = try Data(contentsOf: fileURL)
let appData = try JSONDecoder().decode(AppData.self, from: data)
// do something with appData
} catch {
print(error)
}
When you want to update, you serialize the model objects into a Data containing your JSON and then write it to the file, replacing the file.
do {
let fileURL = try FileManager.default
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("appdata.json")
let data = try JSONEncoder().encode(appData)
try data.write(to: fileURL)
} catch {
print(error)
}
Obviously, this assumes that the AppData type (or whatever you call it) conforms to Codable, but you said you were familiar with serialization of JSON. See Encoding and Decoding Custom Types for more information.
I am learning how to write JSON format data to an iPhone. So with one button clicked, I want to save those data to the iPhone. I looked a simple look on how to write some simple text and saved it to the iPhone file and it worked. However, I tried to apply the same idea to JSON data but still haven't figure out. I tried:
Rather than having contents equal to some written text, I tried to put it as my data (jsondata).
But it does not seem working with
try jsondata.write(to: fileURL, atomically: false, encoding: .utf8)
#IBAction func writeFiles(_ sender: Any) {
let file = "\(UUID().uuidString).txt"
let contents = "Testing"
let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let fileURL = dir.appendingPathComponent(file)
do {
try contents.write(to: fileURL, atomically: false, encoding: .utf8)
}
catch {
print("Error: \(error)")
}
}
Sorry I am a bit new and learning Swift at the moment
Thanks!
Here is a simple project for you to clone or browse the source files
https://github.com/eSpecialized/WriteJsonToFiles
The View Controller contains the example;
https://github.com/eSpecialized/WriteJsonToFiles/blob/master/WriteToFiles/ViewController.swift
Basically you need to do a few steps for writing;
Assemble your objects (Strings etc).
Convert to JSONEncoded Data using the JSONEncoder.
The actual 'Data' Object can write it's binary content to storage.
To read the data;
You need a JSONDecoder.
You need the FileContents in memory using Data(contents:...)
Decode the in-memory data back into the object like it was stored.
A more complicated example of reading and writing JSON to a Web Service API is on Medium here > https://medium.com/better-programming/json-parsing-in-swift-2498099b78f
The concepts are the same, the destination changes from a file in the users document folder to a web service API. I feel that it is adequate to get you started with reading and writing JSON.
The Medium article goes a step further with introducing you to Data Models and reading and writing those Data Model Structs using the Codable protocol.
Enjoy! :)
I got little problem, I am downloading PDF and saving to location, I am using write method to save to location and everything seems to work on version iOS 10 and below, but I got problem with iOS11, I am getting false from method below, I checked path, and bytes and it is same on both devices.
(try? data.write(to: invoiceFileUrl, options: [.atomic])) != nil
try this
let data = NSData(contentsOf:url! as URL)
data?.write(to:invoiceFileUrl, atomically: true)
Have you tried simply moving the file around? I am guessing you are using URLSession to download said file ( or you should anyway ).
Then you can use FileManager to move the temporary file to your documents or whatever desired directory in your app's container.
FileManager.default.moveItem(at:temporaryURL, to: destinationURL)
In case you prefer using paths there is also:
FileManager.default.moveItem(atPath: temporaryPath, toPath: destinationPath)
Though I personally don't use paths often (anymore) because it can lead to a whole load of inconsistencies and other issues you just don't want to deal with. Whatever backend you have try to move to NSURL or URL as quickly as you can with any received data; it'll make your life easier and most importantly more consistent accross foundation API's.
moveItemAtURL - Foundation documentation
I am using
let data = try?
NSString(contentsOfFile: "/Users/BenA**** 1/Desktop/textFile.txt",
encoding: String.Encoding.utf8.rawValue)
to access a file on my computer. When I test put the app on my phone, it obviously didn't work because it didn't have access to the file. How could I put this file on my phone and be able to access it from there? Thanks.
Include your "textFile.txt" in main bundle by drag and drop file in to xcode project, then access it by
class testViewController: UIViewController{
var data:String = ""
override func viewDidLoad() {
super.viewDidLoad()
let path = NSBundle.mainBundle().pathForResource("textFile", ofType: "txt")
data = try? NSString(contentsOfFile: path!, encoding: ENCODING)
}}
Try this way to access 'data' in rest of code.
Hope this help.
To simulate the behavior of iOS enable the sandbox on macOS.
Now you can access files only in the container of the app.
Nevertheless the predefined folders like Documents, Library etc. still exist.
To have access to that directories there a method of NSFileManager. For example one of the most important directories in iOS is the Documents directory.
let documentsFolderURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomain: .UserDomainMask).first!
You have to use that method each time you need to get the URL of that particular directory because for security reasons the absolute path will change.
I have extracted OSX English language dictionary and want to use it in my Swift iPhone app. It has about 236,000 words which I have added to a swift string array.
When I try to run the build, it takes a long time to compile and then throws Segmentation Fault 11
Is this because the array is too big?
Am I going the correct path trying to add english dictionary in my project?
You should probably not store this as a single string. There are more efficient data structures that you can use, such as a trie. You should also consider not loading the entire content into memory at one point but be able to navigate it from the filesystem.
I was able to solve this problem by adding the actual dictionary text file into my xcode project. then utilize below code to fill words from the file to an array. it was pretty fast.
let path = NSBundle.mainBundle().pathForResource("dict2", ofType: "txt")
let dico = String(contentsOfFile: path!, encoding: NSUTF8StringEncoding, error: nil)
let dict = dico!.componentsSeparatedByString("\n")
Hope it helps someone.