Creating separate files - ios

I am looking for a way to create separate files. For example: I have an application which has a form such as name, date etc. and when I press save, I want to save all the information in that form of the user into a file. Each time the form is filled in, I want a new file to be created for each user. The file will be saved in the documents directory of the application. The name of the file should come from the 'name' textfield of the form. I have looked at ways to create files in swift but the filename is always hardcoded in the path url and every time the form is filled out the information gets saved on the same file. I don't want to do this. Any solution or advice is appreciated, I can't seem to find any solution to this online (only how to create a file with a hardcoded file name, which I am already able to do).
import UIKit
class FormViewController: UIViewController {
#IBOutlet weak var organdonationno: UITextField!
#IBOutlet weak var donorage: UITextField!
#IBOutlet weak var snodname: UITextField!
#IBOutlet weak var donorshospital: UITextField!
#IBOutlet weak var date: UITextField!
#IBOutlet weak var ID: UITextField!
var fileMgr: FileManager = FileManager.default
var docsDir: String?
var dataFile: String?
override func viewDidLoad() {
super.viewDidLoad()
let filemgr = FileManager.default
let dirPaths = filemgr.urls(for: .documentDirectory, in: .userDomainMask)
dataFile = dirPaths[0].appendingPathComponent("path").path
if filemgr.fileExists(atPath: dataFile!) {
let databuffer1 = filemgr.contents(atPath: dataFile!)
let databuffer2 = filemgr.contents(atPath: dataFile!)
let databuffer3 = filemgr.contents(atPath: dataFile!)
let databuffer4 = filemgr.contents(atPath: dataFile!)
let databuffer5 = filemgr.contents(atPath: dataFile!)
let datastring1 = NSString(data: databuffer1!, encoding: String.Encoding.utf8.rawValue)
let datastring2 = NSString(data: databuffer2!, encoding: String.Encoding.utf8.rawValue)
let datastring3 = NSString(data: databuffer3!, encoding: String.Encoding.utf8.rawValue)
let datastring4 = NSString(data: databuffer4!, encoding: String.Encoding.utf8.rawValue)
let datastring5 = NSString(data: databuffer5!, encoding: String.Encoding.utf8.rawValue)
organdonationno.text = datastring1 as? String
donorage.text = datastring2 as? String
snodname.text = datastring3 as? String
donorshospital.text = datastring4 as? String
date.text = datastring5 as? String
}
// Do any additional setup after loading the view.
}
#IBAction func savdata(_ sender: Any) {
let databuffer1 = (organdonationno.text)!.data(using: String.Encoding.utf8)
let databuffer2 = (donorage.text)!.data(using: String.Encoding.utf8)
let databuffer3 = (snodname.text)!.data(using: String.Encoding.utf8)
let databuffer4 = (donorshospital.text)!.data(using: String.Encoding.utf8)
let databuffer5 = (date.text)!.data(using: String.Encoding.utf8)
fileMgr.createFile(atPath: dataFile!, contents: databuffer1, attributes: nil)
fileMgr.createFile(atPath: dataFile!, contents: databuffer2, attributes: nil)
fileMgr.createFile(atPath: dataFile!, contents: databuffer3, attributes: nil)
fileMgr.createFile(atPath: dataFile!, contents: databuffer4, attributes: nil)
fileMgr.createFile(atPath: dataFile!, contents: databuffer5, attributes: nil)
}
}

It only saves dates because you are creating file for every field by the same name. It overwritten by new file every time. Last file u create is of date, hence only date persists.If you want to save all form data for one user you can do it as below. You need to create dynamic filename for every form so it is overwritten.
#IBAction func savdata(_ sender: Any) {
let fileName="\(organdonationno.text) + \(donorage.text) + \(snodname.text) + \(donorshospital.text) + (date.text) "
let file = "\(fileName).txt" //this is the file. we will write to and read from it
let text = fileName //just a text
if let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
let path = dir.appendingPathComponent(file)
//writing
do {
try text.write(to: path, atomically: false, encoding: String.Encoding.utf8)
}
catch {/* error handling here */}
//reading
do {
let text2 = try String(contentsOf: path, encoding: String.Encoding.utf8)
}
catch {/* error handling here */}
}
------EDIT--------
To remove Optional word to need to force unwrap the value by putting exclamation mark (!) after the object to need to unwrap.
var canBeNil : String? = "shubhra"
print(canBeNil) // output Optional("shubhra")
print(canBeNil!) // output shubhra
if you need title as organdonationno just edit :
let fileName="\(organdonationno.text)"
let text = "\(organdonationno.text) + \(donorage.text) + \(snodname.text) + \(donorshospital.text) + (date.text) "
Just make sure your filename is unique so that is not overwritten.
Hope it helps. Happy Coding!!

My solution seems similar above. You'd better use the info your user input from textfields to organize a unique name of your file. And I strongly recommend you to write a model to stored your property, and do the saving to disk job for your coding clearness' sake. I can give you an example I just wrote on my playground:
struct User {
let organdonationno: NSString
let donorage: NSString
let snodName: NSString
let donorshospital: NSString
let date: NSString
let ID: NSString
func saveToJSONFile() {
guard let documentDirectoryUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
let pathComponent = ID.appending(".json")
let array = NSArray(array: [organdonationno, donorage, snodName, donorshospital, date, ID])
let fileURL = documentDirectoryUrl.appendingPathComponent(pathComponent)
do {
let data = try JSONSerialization.data(withJSONObject: array, options: [])
try data.write(to: fileURL, options: .atomic)
}
catch {
print(error)
}
}
}
The code I'm writing is put the struct's properties into an NSArray that it can be easily serialized as a JSON object. JSON is a type of well-displayed data structure. When it's written to your disk file, you can open the .json file to see inside. It will be like [ string1, string2, string3, string4, string5, string6] thing, which is pretty easy to understand.
and you can use it this way:
let user = User(organdonationno: datastring1, donorage: datastring2, snodName: datastring3, donorshospital: datastring4, date: datastring5, ID: datastring6)
user.saveToJSONFile()
If you're using swift 4, there is a convenient way to turn the User struct in to JSON object. You just need to let the User conform to Codable, and the apple's underlying implementation will do all of the work.
Struct User: Codable {

Related

How to create a CSV file using Swift

In an app I created to collect data from Apple pencil input, I tried to export the data into a CSV file. But so far, I only managed to create a single column which records the time length. I want to add another column to record the force from the Apple pencil.
This is what I have tried to do:
var patientsData:[Dictionary<String, AnyObject>] = Array()
var dct = Dictionary<String, AnyObject>()
// MARK: CSV writing
func createCSVX(from recArray:[Dictionary<String, AnyObject>]) {
var csvString = "\("Time")\n"
dct.updateValue(TestDraw.time as AnyObject, forKey: "T")
csvString = csvString.appending("\(String(describing: dct["T"]))\n")
patientsData.append(dct)
let fileManager = FileManager.default
do {
let path = try fileManager.url(for: .documentDirectory, in: .allDomainsMask, appropriateFor: nil, create: false)
let fileURL = path.appendingPathComponent("TrailTime.csv")
try csvString.write(to: fileURL, atomically: true, encoding: .utf8)
} catch {
print("error creating file")
}
}
I know I can write another function to create another CSV file with a single column to record the force, but I would like to record them in a single spreadsheet​.
Also, does anyone know how to remove the "Optional" in the CSV file created?
This is what I have tried based on one of the answers.
func createCSVX(from recArray:[Dictionary<String, AnyObject>]) {
var csvString = "\("Time"),\("Force")\n"
dct.updateValue(TestDraw.time as AnyObject, forKey: "T")
dct.updateValue(TestDraw.force as AnyObject, forKey: "F")
patientsData.append(dct)
csvString = csvString.appending("\(String(describing: dct["T"])), \(String(describing: dct["F"]))\n")
let fileManager = FileManager.default
do {
let path = try fileManager.url(for: .documentDirectory, in: .allDomainsMask, appropriateFor: nil , create: false )
let fileURL = path.appendingPathComponent("TrailTime.csv")
try csvString.write(to: fileURL, atomically: true , encoding: .utf8)
} catch {
print("error creating file")
}
print(TestDraw.force)
}
Tutorial copied from https://iostutorialjunction.com/2018/01/create-csv-file-in-swift-programmatically.html:
Step 1:
Create an array, named as "employeeArray" which will store all our records for the employees as key value objects. Also we will add dummy data to the newly created array
class ViewController: UIViewController {
var employeeArray:[Dictionary<String, AnyObject>] = Array()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
for i in 1...10 {
var dct = Dictionary<String, AnyObject>()
dct.updateValue(i as AnyObject, forKey: "EmpID")
dct.updateValue("NameForEmplyee id = \(i)" as AnyObject, forKey: "EmpName")
employeeArray.append(dct)
}
}
}
Step 2: Now we have data with us, and its time to create CSV(comma separated values) file using swift programmatically. For this we will loop through our records in "employeeArray" and append them in a string. Then we will write this string to our document directory of the app. All the stuff goes in different function named as "createCSV", below is the code for the same
func createCSV(from recArray:[Dictionary<String, AnyObject>]) {
var csvString = "\("Employee ID"),\("Employee Name")\n\n"
for dct in recArray {
csvString = csvString.appending("\(String(describing: dct["EmpID"]!)) ,\(String(describing: dct["EmpName"]!))\n")
}
let fileManager = FileManager.default
do {
let path = try fileManager.url(for: .documentDirectory, in: .allDomainsMask, appropriateFor: nil, create: false)
let fileURL = path.appendingPathComponent("CSVRec.csv")
try csvString.write(to: fileURL, atomically: true, encoding: .utf8)
} catch {
print("error creating file")
}
}
Step 3: Finally we will call our function from "viewDidLoad". Below is the complete code
class ViewController: UIViewController {
var employeeArray:[Dictionary<String, AnyObject>] = Array()
override func viewDidLoad() {
super.viewDidLoad()
for i in 1...10 {
var dct = Dictionary<String, AnyObject>()
dct.updateValue(i as AnyObject, forKey: "EmpID")
dct.updateValue("NameForEmplyee id = \(i)" as AnyObject, forKey: "EmpName")
employeeArray.append(dct)
}
createCSV(from: employeeArray)
}
func createCSV(from recArray:[Dictionary<String, AnyObject>]) {
var csvString = "\("Employee ID"),\("Employee Name")\n\n"
for dct in recArray {
csvString = csvString.appending("\(String(describing: dct["EmpID"]!)) ,\(String(describing: dct["EmpName"]!))\n")
}
let fileManager = FileManager.default
do {
let path = try fileManager.url(for: .documentDirectory, in: .allDomainsMask, appropriateFor: nil, create: false)
let fileURL = path.appendingPathComponent("CSVRec.csv")
try csvString.write(to: fileURL, atomically: true, encoding: .utf8)
} catch {
print("error creating file")
}
}
}
Excellent answer above, made a slight modification to address specific instances in the data. You can modify individual components as needed and remove commas, trim UUIDs, etc. Note this solution uses transactions stored in a list of Core Data objects. I also print the location of the data file so you can check it in the simulator.
func createCSVFile() {
var csvString = "id,name,description,category,date,type,receipt,amount\n"
for trans in transactions {
let transID = trans.id!.debugDescription.split(separator: "-")[0].replacingOccurrences(of: ")", with: "")
let transName = trans.name!
let transDesc = trans.desc!.replacingOccurrences(of: ",", with: "-")
let transCat = trans.category!
let transDate = trans.date!
let transType = trans.type!
var transReceipt = "None"
if trans.receipt == nil {
transReceipt = "Present"
}
let transAmount = trans.amount
let dataString = "\(transID),\(transName),\(transDesc),\(transCat),\(transDate),\(transType),\(transReceipt),\(transAmount)\n"
print("DATA: \(dataString)")
csvString = csvString.appending(dataString)
}
let fileManager = FileManager.default
do {
let path = try fileManager.url(for: .documentDirectory, in: .allDomainsMask, appropriateFor: nil, create: false)
print("PATH: \(path)")
let fileURL = path.appendingPathComponent("CSVData.csv")
try csvString.write(to: fileURL, atomically: true, encoding: .utf8)
} catch {
print("error creating file")
}
}

Convert String to Array Items

The below code create the file and puts the data in it but it saves as the following:
["123", "456", "", "789"] How do I get a new item for each paragraph? the idea is the song lyrics would save to the file based on where the verses start and end. I am new to swift so there may be a better way to do this
#IBOutlet weak var songName: UITextField!
#IBOutlet weak var newLyrics: UITextView!
#IBAction func saveSong(_ sender: Any) {
let sName = songName.text!
let fileManager = FileManager.default
let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let path = documentDirectory.appending("/Lyrics/\(sName).plist")
let lyrics = newLyrics.text.components(separatedBy: "\n\n")
print(lyrics)
if let tDocumentDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first {
let filePath = tDocumentDirectory.appendingPathComponent("Lyrics")
if !fileManager.fileExists(atPath: filePath.path) {
do {
try fileManager.createDirectory(atPath: filePath.path, withIntermediateDirectories: true, attributes: nil)
} catch {
NSLog("Couldn't create document directory")
}
}
NSLog("Document directory is \(filePath)")
}
if(!fileManager.fileExists(atPath: path)){
print(path)
var lyricArray = [String]()
lyricArray.append("\(lyrics)")
// any other key values
let lyricData = NSArray(array: lyricArray)
let answer = lyricData.write(toFile: path, atomically: true)
print("File Created? \(answer)")
} else {
print("File Exists")
}
I expect the output to be
Item0 "123"
Item1 "456"
actual output is
Item0 ["123", "456", "", "789"]
You are appending the description of an array to lyricArray with string interpolation (\(...)).
The description of an array is one literal string with format "[\"item1\", \"item2\"]".
You have to append the contents of the array
Replace
lyricArray.append("\(lyrics)")
with
lyricArray.append(contentsOf: lyrics)
or drop lyricArray completely and simply write
let lyricData = NSArray(array: lyrics)
or – highly recommended –
do {
let lyricData = try PropertyListSerialization.data(fromPropertyList: lyrics, format: .xml, options: 0)
} catch { print(error) }
Notes:
Don't use NSArray to handle property lists in Swift. Use PropertyListSerialization.
Never print meaningless literal strings in a catch clause like Couldn't do that. Print the error.
And why do get the path to the documents folder twice with different API (okay, first the path and then the URL)?

An efficient way of writing to file, swift

I'm getting data from sensors using bluetooth, I want to append the string of data I get to the end of file.
When I tried the regular approach
if let dir = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.AllDomainsMask, true).first {
let path = NSURL(fileURLWithPath: dir).URLByAppendingPathComponent(self.file)
do {
try text.writeToURL(path, atomically: false, encoding: NSUTF8StringEncoding)
}
catch {/* error handling here */}
My app started to slow down until even labels were not updating anymore.
Tried using dispatch_async to do in background thread but still it was slowing down my app.
What approach should I use? I read sth about stream but failed to find some solutions in swift I could rely on
Probably your bluetooth is reading data faster than you are performing your file operations. You can optimize it by appending the text to the file instead of reading all the content on each write operation. You could also reuse the file handler between writes and keep the file open.
This sample is extracted from this answer:
struct MyStreamer: OutputStreamType {
lazy var fileHandle: NSFileHandle? = {
let fileHandle = NSFileHandle(forWritingAtPath: self.logPath)
return fileHandle
}()
lazy var logPath: String = {
let path : NSString = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.AllDomainsMask, true).first!
let filePath = (path as NSString).stringByAppendingPathComponent("log.txt")
if !NSFileManager.defaultManager().fileExistsAtPath(filePath) {
NSFileManager.defaultManager().createFileAtPath(filePath, contents: nil, attributes: nil)
}
print(filePath)
return filePath
}()
mutating func write(string: String) {
print(fileHandle)
fileHandle?.seekToEndOfFile()
fileHandle?.writeData(string.dataUsingEncoding(NSUTF8StringEncoding)!)
}
}
Then, you can create a single streamer and reuse it in different writes:
var myStream = MyStreamer()
myStream.write("First of all")
myStream.write("Then after")
myStream.write("And, finally")
In this case, you have the bonus that MyStreamer is also a OutputStreamType, so you can use it like this:
var myStream = MyStreamer()
print("First of all", toStream: &myStream )
print("Then after", toStream: &myStream)
print("And, finally", toStream: &myStream)
Finally I'd recommend you to move 'log.txt' string to a instance variable and pass it as a constructor parameter:
var myStream = MyStreamer("log.txt")
More info about file handler in the Apple Docs.
update #redent84's, to work in Swift 5
Code:
struct MyStreamer{
lazy var fileHandle = FileHandle(forWritingAtPath: logPath)
lazy var logPath: String = {
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .allDomainsMask, true)[0]
let filePath = path + "/log.txt"
if FileManager.default.fileExists(atPath: filePath) == false{
FileManager.default.createFile(atPath: filePath, contents: nil, attributes: nil)
}
print(filePath)
return filePath
}()
mutating func write(_ string: String) {
print(fileHandle?.description ?? "呵呵")
fileHandle?.seekToEndOfFile()
if let data = string.data(using: String.Encoding.utf8){
fileHandle?.write(data)
}
}
}
Usage:
var myStream = MyStreamer()
myStream.write("First of all")
myStream.write("Then after")
myStream.write("And, finally")
try to write file like this..
var paths: [AnyObject] = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let filePath = paths[0].stringByAppendingString("/filename.mov")
do
{
try NSFileManager.defaultManager().removeItemAtURL(outputURL)
}
catch
{
error as NSError
}
do {
try text.writeToURL(path, atomically: false, encoding: NSUTF8StringEncoding)
}
I mean to say at last is you have to remove first.. If any query you can ask me

Trouble working with basic file system functionality in Swift 2.0

Trying to create a simple example code block in Swift 2.0 on iOS 9.1 using Xcode 7.1. Tried this article in techotopia; which I suspect is based on swift 1.2.
Made a few tiny changes so that it would compile & run, but although it appears to work, it doesn't seem to save my string into the file. Is there capability or something subtle I have missed here.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var textBox: UITextField!
var fileMgr: NSFileManager = NSFileManager.defaultManager()
var docsDir: String?
var dataFile: String?
var string: String = ""
override func viewDidLoad() {
super.viewDidLoad()
let dirPaths = NSSearchPathForDirectoriesInDomains(
.DocumentDirectory, .UserDomainMask, true)
docsDir = dirPaths[0] as String
let dataFile = NSURL(fileURLWithPath: docsDir!).URLByAppendingPathComponent("datafile.dat")
string = "\(dataFile)"
print(string)
if fileMgr.fileExistsAtPath(string) {
let databuffer = fileMgr.contentsAtPath(string)
let datastring = NSString(data: databuffer!,
encoding: NSUTF8StringEncoding)
textBox.text = datastring as? String
}
}
#IBAction func saveText(sender: AnyObject) {
let databuffer = (textBox.text)
let data = databuffer?.dataUsingEncoding(NSUTF8StringEncoding)
fileMgr.createFileAtPath(string, contents: data,
attributes: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
More testing; noticed I get this error when I try and create the file... error in __connection_block_invoke_2: Connection interrupted; which explains why it isn't working, if only I could workout what it is talking about?
Continued to try to debug; added UIFileSharingEnabled but cannot see Documents directory; added more code to test its presence, and create it if missing; fails telling me it is already there... even if it is evidently invisible...
When you do this, string ends up being a string representation of the file URL, e.g. file://.... That file:// prefix is a URL "scheme" (like http:// or ftp://). But including the scheme at the start of the string means that this is not a valid path. You have to remove the scheme.
The easiest way to do this is to use the path method to get the path from a NSURL without that scheme. I'd also use URLForDirectory to get the URL for the documents folder nowadays.
class ViewController: UIViewController {
#IBOutlet weak var textBox: UITextField!
lazy var fileMgr = NSFileManager.defaultManager()
var path: String!
override func viewDidLoad() {
super.viewDidLoad()
let documents = try! fileMgr.URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: false)
path = documents.URLByAppendingPathComponent("datafile.dat").path
if fileMgr.fileExistsAtPath(path) {
if let data = fileMgr.contentsAtPath(path) {
textBox.text = String(data: data, encoding: NSUTF8StringEncoding)
}
}
}
#IBAction func saveText(sender: AnyObject) {
let data = textBox.text?.dataUsingEncoding(NSUTF8StringEncoding)
fileMgr.createFileAtPath(path, contents: data, attributes: nil)
}
}
Or I might stay entirely in the world of URLs, retiring paths altogether, also using methods that throw meaningful error messages:
class ViewController: UIViewController {
#IBOutlet weak var textBox: UITextField!
lazy var fileMgr = NSFileManager.defaultManager()
var fileURL: NSURL!
override func viewDidLoad() {
super.viewDidLoad()
do {
let documents = try fileMgr.URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: false)
fileURL = documents.URLByAppendingPathComponent("datafile.dat")
var reachableError: NSError?
if fileURL.checkResourceIsReachableAndReturnError(&reachableError) {
textBox.text = try String(contentsOfURL: fileURL)
}
} catch {
print(error)
}
}
#IBAction func saveText(sender: AnyObject) {
do {
try textBox.text?.writeToURL(fileURL, atomically: true, encoding: NSUTF8StringEncoding)
} catch {
print(error)
}
}
}

iOS Content Blocking Extension Loading Multiple JSON Files

Is it possible to return multiple JSON files from a Content Blocker Extension? In my UI users enable / disable different filters and each filter is represented by a separate file. I currently have (which only loads one despite iterating through multiple):
func beginRequestWithExtensionContext(context: NSExtensionContext) {
var items = Array <NSExtensionItem>()
let resources = ["a", "b", "c"]
for resource in resources {
let url = NSBundle.mainBundle().URLForResource(resource, withExtension: "json")
if let attachment = NSItemProvider(contentsOfURL: url) {
let item = NSExtensionItem()
item.attachments = [attachment]
items.append(item)
}
}
context.completeRequestReturningItems(items, completionHandler: nil)
}
I've tried doing multiple items and a single item with multiple attachments. If it isn't possible to have separate files, any way to combine multiple (or generate programmatically)?
It is possible to have multiple JSON files and use it for the Content Blocker extension.
1) Throws SFContentBlockerErrorDomain when you pass multiple extension items to completeRequestReturningItems method.
2) Can't attach multiple attachments to NSExtension. The comment on the source code says, the attachment is not meant to be an array of alternate data formats/types, but instead a collection to include in a social media post for example. These items are always typed NSItemProvider. I reckon you wouldn't be able to add multiple JSON data as attachments, since they are not a series of attachments to create a message.
My Solution (Verified it works):
NSItemProvider can be initialised with item (NSData) and typeIdentifier.
let aData = NSData(contentsOfURL: NSBundle.mainBundle().URLForResource("a", withExtension: "json")!)
let bData = NSData(contentsOfURL: NSBundle.mainBundle().URLForResource("b", withExtension: "json")!)
aJSON = `convert aData to JSON`
bJSON = `convert bData to JSON`
combinedJSON = `aJSON + bJSON`
combinedData = 'convert combinedJSON to NSData'
let attachment = NSItemProvider(item: combinedData, typeIdentifier: kUTTypeJSON as String)
Now you could create the extension with the attachment, combinedData as per your preferences.
For those curious I ended up adding code to dynamically generate a JSON file (persisted to disk). From other answers it seems like the step of saving could be avoided by returning an NSData representation of the file instead - although that attempt failed for me. Here's my snippet:
import UIKit
import MobileCoreServices
class ActionRequestHandler: NSObject, NSExtensionRequestHandling {
func beginRequestWithExtensionContext(context: NSExtensionContext) {
let item = NSExtensionItem()
let items = [item]
let url = buildJSONFileURL()
if let attachment = NSItemProvider(contentsOfURL: url) { item.attachments = [attachment] }
context.completeRequestReturningItems(items, completionHandler: nil)
}
func buildJSONFileURL() -> NSURL {
let directories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let directory = directories[0]
let path = directory.stringByAppendingFormat("/block.json")
let selector = [...] // Dynamically Generated
let dictionary = [[
"action": [ "type": "css-display-none", "selector": selector ],
"trigger": [ "url-filter": ".*" ]
]]
let data = try! NSJSONSerialization.dataWithJSONObject(dictionary, options: NSJSONWritingOptions.PrettyPrinted)
let text = NSString(data: data, encoding: NSASCIIStringEncoding)!
try! text.writeToFile(path, atomically: true, encoding: NSASCIIStringEncoding)
return NSURL(fileURLWithPath: path)
}
}
You can combine two JSON rule files in to one file and use that file.
import UIKit
import MobileCoreServices
class ContentBlockerRequestHandler: NSObject, NSExtensionRequestHandling {
func beginRequest(with context: NSExtensionContext) {
let sharedContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "you app group identifier")
let sourceURLRules = sharedContainerURL?.appendingPathComponent("Rules1.json")
let sourceURLRules2 = sharedContainerURL?.appendingPathComponent("Rules2.json")
do {
let jsonDecoder = JSONDecoder()
let dataFormRules1 = try Data(contentsOf: sourceURLRules1!, options: .mappedIfSafe)// Rule is Decode able Swift class
let rulesArray1 = try? jsonDecoder.decode(Array<Rule>.self,from: dataFormRules1)
let dataFormRules2 = try Data(contentsOf: sourceURLRules2!, options: .mappedIfSafe)
let rulesArray2 = try? jsonDecoder.decode(Array<Rule>.self,from: dataFormRules2)
saveCombinedRuleFile(ruleList: rulesArray1! + rulesArray2!)
} catch {
//handle error condition
}
let sourceURLCombinedRule = sharedContainerURL?.appendingPathComponent("CombinedRule.json")
let combinedRuleAttachment = NSItemProvider(contentsOf: sourceURLCombinedRule)
let item = NSExtensionItem()
item.attachments = [combinedRuleAttachment]
context.completeRequest(returningItems: [item], completionHandler: nil)
}
func saveCombinedRuleFile(ruleList:[Rule]) {
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(ruleList) {
let sharedContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "you app group identifier")
if let json = String(data: encoded, encoding: .utf8) {
print(json)
}
if let destinationURL = sharedContainerURL?.appendingPathComponent("CombinedRule.json") {
do {
try encoded.write(to: destinationURL)
} catch {
print ("catchtry")
}
}
}
}
}

Resources