I am unable to write array of dicts into plist file. I can write Dictionary to plist file is ok.
My code is as follows; It is always not success
if var plistArray = NSMutableArray(contentsOfFile: path)
{
plistArray.add(dict)
let success = plistArray.write(toFile: path, atomically: true)
if success
{
print("success")
}
else
{
print("not success")
}
}
What might be wrong?
BR,
Erdem
First of all do not use NSMutableArray in Swift at all, use native Swift Array.
Second of all don't use the Foundation methods to read and write Property List in Swift, use PropertyListSerialization.
Finally Apple highly recommends to use the URL related API rather than String paths.
Assuming the array contains
[["name" : "foo", "id" : 1], ["name" : "bar", "id" : 2]]
use this code to append a new item
let url = URL(fileURLWithPath: path)
do {
let data = try Data(contentsOf: url)
var array = try PropertyListSerialization.propertyList(from: data, format: nil) as! [[String:Any]]
array.append(["name" : "baz", "id" : 3])
let writeData = try PropertyListSerialization.data(fromPropertyList: array, format: .xml, options:0)
try writeData.write(to: url)
} catch {
print(error)
}
Consider to use the Codable protocol to be able to save property list compliant classes and structs to disk.
I think you are missing the serialization part, you need to convert your plist file to data first and then write to file.
let pathForThePlistFile = "your plist path"
// Extract the content of the file as NSData
let data = FileManager.default.contents(atPath: pathForThePlistFile)!
// Convert the NSData to mutable array
do{
let array = try PropertyListSerialization.propertyList(from: data, options: PropertyListSerialization.MutabilityOptions.mutableContainersAndLeaves, format: nil) as! NSMutableArray
array.add("Some Data")
// Save to plist
array.write(toFile: pathForThePlistFile, atomically: true)
}catch{
print("An error occurred while writing to plist")
}
Related
New to IOS development and JSON stuff. I have a struct for Recipe which includes things like name, ingredients, instructions, etc. I have an array of Recipes. When my app is first run, I read data from a JSON file into the array of recipes so the app isn't empty at first. Throughout the app I append to the array of recipes. How would I go about writing the array back to the file everytime the array is changed? Here is some of the code and things I have tried.
Recipe Struct:
struct Recipe : Codable, Identifiable {
var id: String
var name: String
var ingredients: [String]
var instructions: [String]
var category: String
var imageName: String
}
Reading from JSON into recipe array:
import UIKit
import SwiftUI
var recipeData: [Recipe] = loadJson("recipeData.json")
func loadJson<T: Decodable>(_ filename: String) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: filename,withExtension: nil)
else {
fatalError("\(filename) not found.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Could not load \(filename): \(error)")
}
do {
return try JSONDecoder().decode(T.self, from: data)
} catch {
fatalError("Unable to parse \(filename): \(error)")
}
}
My attempt to write back to a json file once array is changed(appended to):
func writeJson(){
var jsonArray = [String]()
if let documentDirectory = FileManager.default.urls(for: .documentDirectory,
in: .userDomainMask).first {
let pathWithFilename = documentDirectory.appendingPathComponent("test.json")
for recipe in recipeData{
do{
let encodedData = try JSONEncoder().encode(recipe)
let jsonString = String(data: encodedData, encoding: .utf8)
print(jsonString!)
jsonArray.append(jsonString!)
try jsonString!.write(to: pathWithFilename,
atomically: true,
encoding: .utf8)
}catch{
print(error)
}
}
}
}
This all builds successfully but nothing is written to my tests.json file. I am very new so any help would be appreciated. Thank you.
try jsonString!.write(to: pathWithFilename,
atomically: true,
encoding: .utf8)
This method erases the existing file and replaces it with the data in question. You do this in a loop, so you're going to overwrite the file many times, always with a single recipe. My expectation is that this would always leave just the last recipe in the file.
I believe what you meant to do is:
// Encode all the recipes, not one at a time.
let encodedData = try JSONEncoder().encode(recipeData)
// Write them. There's no reason to convert to a string
encodedData.write(to: pathWithFilename, options: [.atomic])
As a beginner, this is probably fine. A more professional approach would likely spread this data over multiple files, use a database, or Core Data. But for small projects with only a few data items, writing a single JSON file is fine.
I'm trying to write a simple Dictionary to a .plist file in swift. I have written the code in an extension of the Dictionary class, for convenience. Here's what it looks like.
extension Dictionary {
func writeToPath (path: String) {
do {
// archive data
let data = try NSKeyedArchiver.archivedData(withRootObject: self,
requiringSecureCoding: true)
// write data
do {
let url = URL(string: path)
try data.write(to: url!)
}
catch {
print("Failed to write dictionary data to disk.")
}
}
catch {
print("Failed to archive dictionary.")
}
}
}
Every time I run this I see "Failed to write dictionary data to disk." The path is valid. I'm using "/var/mobile/Containers/Data/Application/(Numbers and Letters)/Documents/favDict.plist".
The dictionary is type Dictionary<Int, Int>, and contains only [0:0] (for simplicity/troubleshooting purposes).
Why doesn't this work? Do you have a better solution for writing the dictionary to disk?
URL(string is the wrong API. It requires that the string starts with a scheme like https://.
In the file system where paths starts with a slash you must use
let url = URL(fileURLWithPath: path)
as WithPath implies.
A swiftier way is PropertyListEncoder
extension Dictionary where Key: Encodable, Value: Encodable {
func writeToURL(_ url: URL) throws {
// archive data
let data = try PropertyListEncoder().encode(self)
try data.write(to: url)
}
}
I have a JSON file and I'm trying to access the array in it.
The JSON file looks like:
{
"cars": [{
"name": "BMW",
"icons": [["front.png", "back.png", "B3"],
["front_red", "back_red", "C4"]
]
}]
}
//cars is an array of dictionaries, I just mentioned one in the snippet.
I get the JSON data as:
func loadJSONData(){
if let path = Bundle.main.path(forResource: "testJSON", ofType: "json")
{
if let jsonData = NSData(contentsOfFile : path)
{
do {
if let jsonResult = try JSONSerialization.jsonObject(with: jsonData as Data, options: JSONSerialization.ReadingOptions.mutableContainers) as? [String:Any]
{
self.testJSONData = (jsonResult["cars"] as? Array)!
//self.testJSONData = (jsonResult["cars"] as? Array<Dictionary<String, Any>>)! //also tried this
}
}
catch let error as NSError {
print(error.localizedDescription)
}
}
}
}
testJSONData is declared as an array:
var testJSONData = [] as [Dictionary<String, Any>]
and the error occurs when trying to get the "icons" array from the JSON.
let namePredicate = NSPredicate(format: "name like BMW")
let filteredArray :Array = testJSONData.filter() { namePredicate.evaluate(with: $0) }
let carData: Dictionary = filteredArray[0] as Dictionary<String, Any>
let carIcons: Array = carData["icons"] as! Array //error at this line
Cannot convert value of type 'Array<_>' to specified type 'Array'
Can someone please show me where I'm doing wrong ? Thanks!
Array is a generic type in Swift, so when you want to declare an array variable, you always need to specific what type of elements the Array is holding. There's no such type as Array without specifying its Element type.
Also, there's no need for type annotations in Swift, the compiler can infer the types for you and you are explicitly telling the compiler the type by casting anyways.
carIcons should be of type Array<Array<String>> or as a shorthand [[String]]
let carIcons = carData["icons"] as! [[String]]
Some general comments about your code: don't use old Foundation types, such as NSData in Swift when they have native Swift equivalents. Also don't do force unwrapping of safe casted types, that makes no sense. Either handle the casting and unwrapping safely or simply force cast if you know the cast will succeed for sure. .mutableContainers have no effect in Swift, so don't use it. There's no need to cast error to NSError in a catch block, Swift has its own Error type.
func loadJSONData(){
if let fileURL = Bundle.main.url(forResource: "testJSON", withExtension: "json") {
do {
let jsonData = try Data(contentsOfFile: fileURL)
if let jsonResult = try JSONSerialization.jsonObject(with: jsonData) as? [String:Any], let cars = jsonResult["cars"] as? [[String:Any]] {
self.testJSONData = cars
} else {
print("Unexpected JSON format")
}
}
catch {
print(error)
}
}
}
However, if you are using Swift 4, the best solution would be using the type safe Codable protocol and JSONDecoder instead of JSONSerialization.
I want read a NSArray form plist , and the code :
func loadPlistArray() -> [Any] {
var path: String? = Bundle.main.path(forResource:"MyCenter", ofType: "plist")
if let arry = NSArray(contentsOfFile: path!) {
return arry as! NSArray
}else{
return nil;
}
}
but always got errors below:
And After I got the data from plist, I fount that I can't see the Details of Dictionary :
And here is my plist:
should I add a generic in the array by var plistArray : [[String:Any]]?
The errors messages you are getting tell you what is wrong with your method, this is how I would write the function:
func loadPlistArray() -> [Any] { // 1
guard
let url = Bundle.main.url(forResource: "MyCenter", withExtension: "plist"), // 2
let list = NSArray(contentsOf: url) as? [Any] // 3
else { return [] } // 4
return list
}
And some commentary:
You are declaring the method to return an Array of Any items, but your method tries to return an NSArray.
It is recommended to use the URL based methods for accessing files, rather then the string based paths.
You have to use the Array methods to read the plist, but you can cast it to [Any]. However, if you know the type of items you have in the plist, I recommend that you return a properly type array from this method e.g. [String], [Int] etc.
You don't need to return an optional if the file can't be read. Depending on how you want to handle the error you could either return an empty array (as I've shown here) or convert your function into a throwing one so that if you can't read the file an error is thrown and can be handled by the calling code.
Your method signature clearly states that it returns an [Any] (i.e., Swift native Array containing elements of any type whatsoever), while you try to cast the return value into NSArray (even though it already is by virtue of intialization: NSArray(contentsOfFile:)).
Change it to:
return arry as? [Any]
// (will return nil if the cast fails - not a problem if you
// also apply the fix mentioned below...)
The other path tries to return nil; for that to be acceptable, your signature needs to be defined as returning an optional:
func loadPlistArray() -> [Any] // WRONG
func loadPlistArray() -> [Any]? // RIGHT
EDIT: If your app is structured in such a way that you can't afford to return nil from your method, you can instead return an empty array on failure:
else {
return [] // Empty array
}
(use [:] for empty dictionary)
Also, try to avoid using ! whenever possible, and switch to ? instead, unless you are 100% sure that whatever it is you are forcing will not fail and cause a runtime error (crash).
return (arry ) as! [Any]
You cannot return NSArray on type Any
I am using something like this in my project.
if let fileUrl = Bundle.main.url(forResource: "new", withExtension: "plist"),
let myDict = NSDictionary(contentsOf: fileUrl) as? [String:Any] {
print(myDict)
}
I have another plist for color which have array as a root.
if let fileUrl = Bundle.main.url(forResource: "color", withExtension: "plist"),
let data = try? Data(contentsOf: fileUrl) {
if let result = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [Any] {
print(result)
}
}
I am getting the following error:
Could not cast value of type '__NSCFDictionary' (0x10dccdef0) to 'NSMutableArray' (0x10dccd978)
I am using this tutorial: http://sweettutos.com/2015/06/03/swift-how-to-read-and-write-into-plist-files/
However, when I use the save function as:
func saveScore() {
// Save note to plist
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let pathForThePlistFile = appDelegate.plistPathInDocument
// Extract the content of the file as NSData
let data:NSData = NSFileManager.defaultManager().contentsAtPath(pathForThePlistFile)!
// Convert the NSData to mutable array
do{
let scoresArray = try NSPropertyListSerialization.propertyListWithData(data, options: NSPropertyListMutabilityOptions.MutableContainersAndLeaves, format: nil) as! NSMutableArray
scoresArray.addObject(self.totalScoreLabel.text!)
// Save to plist
scoresArray.writeToFile(pathForThePlistFile, atomically: true)
}catch{
print("An error occurred while writing to plist")
}
The scoreArray is:
var scoresArray:NSMutableArray!
For the pList path, I am using this code in AppDelegate as instructed in the tutorial, thus creating a “scores.plist” file.
var plistPathInDocument:String = String()
func preparePlistForUse(){
// 1
let rootPath = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, .UserDomainMask, true)[0]
// 2
plistPathInDocument = rootPath.stringByAppendingString("/scores.plist")
if !NSFileManager.defaultManager().fileExistsAtPath(plistPathInDocument){
let plistPathInBundle = NSBundle.mainBundle().pathForResource("scores", ofType: "plist") as String!
// 3
do {
try NSFileManager.defaultManager().copyItemAtPath(plistPathInBundle, toPath: plistPathInDocument)
}catch{
print("Error occurred while copying file to document \(error)")
}
}
}
I have made sure that the root of the scores.plist file is “Array”, not a dictionary.
I saw the same question here: Swift. SIGABRT: cast value of type '__NSCFDictionary' to 'NSMutableArray' but it was unanswered, please help.
Here's how my pList looks: