I'm trying to make the conversion from Objc to swift and have had better days.
I have a class with a dictionary:
collaborationDictionary:[String:Set<String>]
I am trying to write/read this dictionary to/from a file and just can't quite seem to make it work. I have to save the dictionary using the following JSON structure and I have to use SwiftyJSON.
{ "Collaborations" : {
"5604" : [
"whiteboard.png",
"VID_20161123_135117.3gp",
"Photo_0.jpeg"]
"5603" : [
"VID_20161123_135117.3gp"],
"5537" : [
"Screenshot_20151212-132454.png",
"VID_20161202_083205.3gp",
"VID_20161123_135117.3gp",
"Photo_0.jpeg",
"Screenshot_20151212-132428.png",
"Screenshot_20151212-132520.png",
"IMG_20161017_132105.jpg",
"whiteboard.png"]}
}
I don't have any real problem with finding/retrieving the file or writing the file. I just can't quite figure out how to manually load SwiftyJSON. I need to have a JSON object called "Collaborations" at the top. It needs to contain a dictionary of collaboration IDs (5604, 5603...). Each collaboration contains an array of string (filenames). I'm including the code I'm using to read/write the file but I need help with the SwiftyJSON library.
This is the member data member I'm using to store the above data:
These are the functions I need to finish:
private var collaborationDictionary:[String:Set<String>] = [:]
func getUploadedFileSet() {
collaborationDictionary = [:]
let documentsURL = URL(string: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
let appURL = documentsURL?.appendingPathComponent(APP_DISTINGUISHED_NAME)
let jsonFileURL = appURL?.appendingPathComponent(UPLOADED_ITEMS_DB_JSON)
if FileManager.default.fileExists(atPath: (jsonFileURL?.absoluteString)!) {
do {
let data = try Data(contentsOf: jsonFileURL!, options: .alwaysMapped)
let json = JSON(data: data)
// ************************************************
// NEED HELP START
// NOW WHAT???? What is the SwiftyJSON code
?????????????????????????
// NEED HELP END
// ************************************************
} catch let error {
print(error.localizedDescription)
}
}
}
func saveUploadedFilesSet() {
let documentsURL = URL(string: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
let appURL = documentsURL?.appendingPathComponent(APP_DISTINGUISHED_NAME)
let jsonFileURL = appURL?.appendingPathComponent(UPLOADED_ITEMS_DB_JSON)
do {
let dirExists = FileManager.default.fileExists(atPath: (appURL?.absoluteString)!)
if !dirExists {
try FileManager.default.createDirectory(atPath: (appURL?.absoluteString)!, withIntermediateDirectories: false, attributes: nil)
}
// ************************************************
// NEED HELP START
// NOW WHAT???? What is the SwiftyJSON code
?????????????????????????
// NEED HELP END
// ************************************************
// Write to file code - haven't written it yet but that should be easy
} catch let error as NSError {
print(error.localizedDescription);
}
}
Any direction would be greatly appreciated. Thanks!
EDIT
I was able to figure out how to load the supplied JSON structure from file. Here is the code:
func getUploadedFileSet() {
let documentsURL = URL(string: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
let appURL = documentsURL?.appendingPathComponent(APP_DISTINGUISHED_NAME)
let jsonFileURL = appURL?.appendingPathComponent(UPLOADED_ITEMS_DB_JSON)
if FileManager.default.fileExists(atPath: (jsonFileURL?.absoluteString)!) {
do {
let data = try Data(contentsOf: jsonFileURL!, options: .alwaysMapped)
let json = JSON(data: data)
if json != nil {
for (key, subJson) in json[kCollaborations] {
let stringArray:[String] = subJson.arrayValue.map { $0.string! }
let stringSet = Set(stringArray)
collaborationDictionary.updateValue(stringSet, forKey: key)
}
} else {
print("Could not get json from file, make sure that file contains valid json.")
}
} catch let error {
print(error.localizedDescription)
}
}
I still haven't figured out how to save the collaborationDictionary object to file. My biggest problem is figuring out how to put in the "Collaborations" key. Any ideas?
I finally got this to work. The biggest problem was that I couldn't convert collaborationDictionary to JSON. I finally had to convert it to a dictionary of arrays vs dictionary of sets. Here are the 2 methods:
// **************************************************************************
func getUploadedFileSet() {
let documentsURL = URL(string: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
let appURL = documentsURL?.appendingPathComponent(APP_DISTINGUISHED_NAME)
let jsonFileURL = appURL?.appendingPathComponent(UPLOADED_ITEMS_DB_JSON)
if FileManager.default.fileExists(atPath: (jsonFileURL?.absoluteString)!) {
do {
let data = try Data(contentsOf: jsonFileURL!, options: .alwaysMapped)
let json = JSON(data: data)
if json != nil {
for (key, subJson) in json[kCollaborations] {
let stringArray:[String] = subJson.arrayValue.map { $0.string! }
let stringSet = Set(stringArray)
collaborationDictionary.updateValue(stringSet, forKey: key)
}
} else {
print("Could not get json from file, make sure that file contains valid json.")
}
} catch let error {
print(error.localizedDescription)
}
}
}
// **************************************************************************
func saveUploadedFilesSet() {
let documentsURL = URL(string: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
let appURL = documentsURL?.appendingPathComponent(APP_DISTINGUISHED_NAME)
let jsonFileURL = appURL?.appendingPathComponent(UPLOADED_ITEMS_DB_JSON)
let adjustedJSONFileURL = URL(fileURLWithPath:(jsonFileURL?.absoluteString)!)
do {
let dirExists = FileManager.default.fileExists(atPath: (appURL?.absoluteString)!)
if !dirExists {
try FileManager.default.createDirectory(atPath: (appURL?.absoluteString)!, withIntermediateDirectories: false, attributes: nil)
}
// Convert set elements to arrays
var convertedCollaborationDictionary: [String:[String]] = [:]
for (sessionID, fileNameSet) in collaborationDictionary {
let array = Array(fileNameSet)
convertedCollaborationDictionary.updateValue(array, forKey: sessionID)
}
let json: JSON = JSON(convertedCollaborationDictionary)
let fullJSON: JSON = [kCollaborations:json.object]
let data = try fullJSON.rawData()
try data.write(to: adjustedJSONFileURL, options: .atomic)
} catch let error as NSError {
print(error.localizedDescription);
}
}
If you dig into the source, SwiftyJSON wraps JSONSerialization, which can both be initialized and converted back to Data which is knows how to read and write itself from disk:
func readJSON() -> JSON? {
guard let url = Bundle.main.url(forResource: "data", withExtension: "json"),
let data = try? Data(contentsOf: url) else {
return nil
}
return JSON(data: data)
}
func write(json: JSON, to url: URL) throws {
let data = try json.rawData()
try data.write(to: url)
}
Note that you can load your static data from anywhere including your Bundle, but you can only write to the sandbox (ie the Documents directory). You may wish to copy from your Bundle to the documents directory on first run if you are planning on reading/writing to the same file.
Also your sample JSON is bad (lint it). You need a comma after "Photo_0.jpeg"]
Related
I'm retrieving a plist file, updating it, and writing it to disk.
1) Retrieval
func pListURL() -> URL? {
guard let result = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("somePlist.plist") else { return nil }
return result
}
2) Update
var data: [String: Int] = [:]
if let url = pListURL() {
do {
let dataContent = try Data(contentsOf: url)
if let dict = try PropertyListSerialization.propertyList(from: dataContent, format: nil) as? [String: Int] {
data = dict
}
} catch {
print(error)
}
}
// update code
3) Write
if let path = pListURL() {
do {
let plistData = try PropertyListSerialization.data(fromPropertyList: data, format: .xml, options: 0)
try plistData.write(to: path)
} catch {
print(error)
}
}
The odd thing is I get an error that says:
The file “somePlist.plist” couldn’t be opened because there is no such file.
even though when I check the plist, it's actually properly created and updated as it should be. As far as I know, the create parameter of FileManager.default.url(for:in:appropriateFor:create: ) ensures that it "creates the directory if it does not already exist", which means somePlist.plist is created if plist doesn't exist already.
As far as I know, the create parameter of FileManager.default.url(for:in:appropriateFor:create: ) ensures that it "creates the directory if it does not already exist", which means somePlist.plist is created if plist doesn't exist already.
No, it means the directory is created but the file is not created.
In the update part ignore the couldn’t be opened error and write the (new) data to disk or check if the file exists with fileExists(atPath.
And you can declare the return value in pListURL as non-optional. It's guaranteed that the folder Documents exists
func pListURL() -> URL {
return try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent("somePlist.plist")
}
Update:
These are reliable versions of update and write
func update(dictionary: [String:Int]) {
let url = pListURL()
guard FileManager.default.fileExists(atPath: url.path) else { write(dictionary: dictionary); return }
do {
let dataContent = try Data(contentsOf: url)
if var dict = try PropertyListSerialization.propertyList(from: dataContent, format: nil) as? [String: Int] {
for (key, value) in dictionary {
dict.updateValue(value, forKey: key)
}
write(dictionary: dict)
} else {
write(dictionary: dictionary)
}
} catch {
print(error)
}
}
func write(dictionary: [String:Int]) {
let url = pListURL()
do {
let plistData = try PropertyListSerialization.data(fromPropertyList: dictionary, format: .xml, options: 0)
try plistData.write(to: url)
} catch {
print(error)
}
}
I know this question already asked but not getting solution.
From this code I will get all the information from the contact but image not found when open vcf files on mac os, also not getting when share this file. I use this stackoverflow link here but It's not help full.
var contacts = [CNContact]()
let keys = [CNContactVCardSerialization.descriptorForRequiredKeys()
] as [Any]
let request = CNContactFetchRequest(keysToFetch: keys as! [CNKeyDescriptor])
do {
try self.contactStore.enumerateContacts(with: request) {
(contact, stop) in
// Array containing all unified contacts from everywhere
contacts.append(contact)
}
} catch {
print("unable to fetch contacts")
}
do {
let data = try CNContactVCardSerialization.data(with: contacts)
if let directoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
let fileURL = directoryURL.appendingPathComponent("contacts").appendingPathExtension("vcf")
print(fileURL)
do {
try data.write(to: fileURL, options: .atomic)
} catch {
print("error \(error)")
}
}
} catch {
print("error \(error)")
}
Probably,
let data = try CNContactVCardSerialization.data(with: contacts)
Only adds the contact info without image tag, and hence you need to add image tag manually into your VCF file. you can find the solution here.
https://stackoverflow.com/a/44308365/5576675
Yes,
let data = try CNContactVCardSerialization.data(with: contacts)
give only contacts info not image data so you need to do like this, you can get correct VCF files.
var finalData = Data()
for contact in contacts {
do {
var data = try CNContactVCardSerialization.data(with: [contact])
var vcString = String(data: data, encoding: String.Encoding.utf8)
let base64Image = contact.imageData?.base64EncodedString()
let vcardImageString = "PHOTO;TYPE=JPEG;ENCODING=BASE64:" + (base64Image ?? "") + ("\n")
vcString = vcString?.replacingOccurrences(of: "END:VCARD", with: vcardImageString + ("END:VCARD"))
data = (vcString?.data(using: .utf8))!
finalData += data
} catch {
print("error \(error)")
}
}
if let directoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
let fileURL = directoryURL.appendingPathComponent("contacts").appendingPathExtension("vcf")
do {
try finalData.write(to: fileURL, options: .atomic)
} catch {
print("error \(error)")
}
}
So, i have an empty plist, i am trying to create these values in the plist
using this code :
let dictionary:[String:String] = ["key1" : "value1", "key2":"value2", "key3":"value3"]
let documentDirectoryURL = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
let fileURL = documentDirectoryURL.appendingPathComponent("dictionary.plist")
if NSKeyedArchiver.archiveRootObject(dictionary, toFile: fileURL.path) {
print(true)
}
if let loadedDic = NSKeyedUnarchiver.unarchiveObject(withFile: fileURL.path) as? [String:String] {
print(loadedDic) // "["key1": "value1", "key2": "value2", "key3": "value3"]\n"
}
everything is fine here, but the question is, when i click the plist in my xcode project, its empty, these values are only printed not inserted to the plist
NSKeyedUnarchiver is the wrong way to save property lists.
There is a dedicated struct PropertyListSerialization to load and save property lists.
First declare a computed property plistURL
var plistURL : URL {
let documentDirectoryURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
return documentDirectoryURL.appendingPathComponent("dictionary.plist")
}
and two methods for loading and saving
func savePropertyList(_ plist: Any) throws
{
let plistData = try PropertyListSerialization.data(fromPropertyList: plist, format: .xml, options: 0)
try plistData.write(to: plistURL)
}
func loadPropertyList() throws -> [String:String]
{
let data = try Data(contentsOf: plistURL)
guard let plist = try PropertyListSerialization.propertyList(from: data, format: nil) as? [String:String] else {
return [:]
}
return plist
}
Create the dictionary and save it
do {
let dictionary = ["key1" : "value1", "key2":"value2", "key3":"value3"]
try savePropertyList(dictionary)
} catch {
print(error)
}
To update a value read it, update the value and save it back
do {
var dictionary = try loadPropertyList()
dictionary.updateValue("value4", forKey: "key4")
try savePropertyList(dictionary)
} catch {
print(error)
}
Have you tried using PropertyListEncoder instead of NSKeyedArchiver?
do {
try PropertyListEncoder().encode(dictionary).write(to: fileURL)
} catch {
print(error)
}
Decode:
do {
let data = try Data(contentsOf: fileURL)
try PropertyListDecoder().decode([String: String].self, from: data)
} catch {
// Handle error
}
Here is my answer
//MARK: User.plist Save & retrive data
func saveUserDataWithParams(userData: AnyObject) -> Void {
let documentsDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString
let path : NSString = documentsDirectory.appendingPathComponent("User.plist") as NSString
//userData.write(path as String, atomically: true)
userData.write(toFile: path as String, atomically: true)
}
func getUserPlistData() -> NSMutableDictionary {
let documentsDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString
let path : NSString = documentsDirectory.appendingPathComponent("User.plist") as NSString
let fileManager = FileManager.default
if (!(fileManager.fileExists(atPath: path as String)))
{
let documentsDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString
let path : NSString = documentsDirectory.appendingPathComponent("User.plist") as NSString
let data : NSMutableDictionary = NSMutableDictionary()
data.write(toFile: path as String, atomically: true)
}
let data : NSMutableDictionary = NSMutableDictionary(contentsOfFile: path as String)!
return data
}
I'd like to use readStringFromURL method to obtain a file from a plist and then use it on insertDataInArrayFromPlist in order to display it or put it on CoreData, substituting let path = Bundle.main.path(forResource: plistFileName, ofType: plistFileExtension).
the ISSUE the try statement gives me this ERROR
Argument labels '(contentsOfURL:, usedEncoding:)' do not match any available overloads
in my viewDidLoad:
let obtainedfile = readStringFromURL(stringURL: kremoteSamplePlist)
print(obtainedfile ?? "nothing to print")
I retrive the file from web
func readStringFromURL(stringURL:String)-> String!{
if let url = NSURL(string: stringURL) {
do {
return try String(contentsOfURL: url, usedEncoding: nil)
} catch {
print("Cannot load contents")
return nil
}
} else {
print("String was not a URL")
return nil
}
}
then I put the data in a struct
func insertDataInArrayFromPlist(arrayOfEntities: inout [product]) {
let path = Bundle.main.path(forResource: plistFileName, ofType: plistFileExtension)
let localArray = NSArray(contentsOfFile: path!)!
for dict in localArray {
var futureEntity = product()
let bdict = dict as! [String: AnyObject]
futureEntity.name = bdict["Name"] as? String
futureEntity.ProductId = bdict["Product Id"] as? String
arrayOfEntities.append(futureEntity)
}
for element in arrayOfEntities {
print("name is \(element.name!), the id is \(element.ProductId!)")
}
}
Theres a library available via Cocoapods, CSV.swift by Yaslab. Allows you to import a csv directly in Swift code and convert to a data type of your own. Does the job for me.
https://github.com/yaslab/CSV.swift
I am trying to use a file called Data.plist to store some simple unstructured data, and I placed this file at the root folder of my app. To make it simple to read/write to this file, I created the following DataManager struct. It can read Data.plist file with no problem, but it cannot write data to the file. I am not sure where the problem is, could anyone spot where might be wrong?
struct DataManager {
static var shared = DataManager()
var dataFilePath: String? {
return Bundle.main.path(forResource: "Data", ofType: "plist")
}
var dict: NSMutableDictionary? {
guard let filePath = self.dataFilePath else { return nil }
return NSMutableDictionary(contentsOfFile: filePath)
}
let fileManager = FileManager.default
fileprivate init() {
guard let path = dataFilePath else { return }
guard fileManager.fileExists(atPath: path) else {
fileManager.createFile(atPath: path, contents: nil, attributes: nil) // create the file
print("created Data.plist file successfully")
return
}
}
func save(_ value: Any, for key: String) -> Bool {
guard let dict = dict else { return false }
dict.setObject(value, forKey: key as NSCopying)
dict.write(toFile: dataFilePath!, atomically: true)
// confirm
let resultDict = NSMutableDictionary(contentsOfFile: dataFilePath!)
print("saving, dict: \(resultDict)") // I can see this is working
return true
}
func delete(key: String) -> Bool {
guard let dict = dict else { return false }
dict.removeObject(forKey: key)
return true
}
func retrieve(for key: String) -> Any? {
guard let dict = dict else { return false }
return dict.object(forKey: key)
}
}
You cannot modify the files inside your app bundle. So all the files that you get with Bundle.main.path(forResource:ofType:) are readable but not writable.
If you want to modify this file you will need to copy it inside your app's document directory first.
let initialFileURL = URL(fileURLWithPath: Bundle.main.path(forResource: "Data", ofType: "plist")!)
let documentDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last!
let writableFileURL = documentDirectoryURL.appendingPathComponent("Data.plist", isDirectory: false)
do {
try FileManager.default.copyItem(at: initialFileURL, to: writableFileURL)
} catch {
print("Copying file failed with error : \(error)")
}
// You can modify the file at writableFileURL