How can I read Plist in Swift? - ios

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)
}
}

Related

Xcode Swift how do I print a value form my custom plist file?

Ok I have read so much about NSArray NSDictionary I'm lost now, what I want is to print the value 'name' from the first array item of my custom plist.
This is my plist:
and this is my code in my ViewController.swift:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let path = Bundle.main.path(forResource: "test", ofType: "plist")
let dic = NSArray(contentsOfFile: path!)
print(dic?.firstObject)
}
}
in my console I see:
Optional({
active = 0;
name = "John Doe";
})
I would think that print(dic?.firstObject["name"]) would do the trick
but I get an error: Value of type 'Any?' has no subscripts
So how do I print the values of name and active of my first array?
I know there are lots of answers on SO regarding this question, that's the reason I got so far.
but I just don't know how to fix this.
Kind regards,
Ralph
First of all please never use the NSArray/NSDictionary related API in Swift to read a property list. You are throwing away the type information.
However you can read the values with
let array = NSArray(contentsOfFile: path!) as! [[String:Any]]
for item in array {
let name = item["name"] as! String
let active = item["active"] as! Bool
print(name, active)
}
The dedicated and recommended API is PropertyListSerialization :
let url = Bundle.main.url(forResource: "test", withExtension: "plist")!
let data = try! Data(contentsOf: url)
let array = try! PropertyListSerialization.propertyList(from: data, format: nil) as! [[String:Any]]
A better way is the Codable protocol and PropertyListDecoder
struct User : Decodable {
let name : String
let active : Bool
}
override func viewDidLoad() {
super.viewDidLoad()
let url = Bundle.main.url(forResource: "test", withExtension: "plist")!
let data = try! Data(contentsOf: url)
let array = try! PropertyListDecoder().decode([User].self, from: data)
for item in array {
print(item.name, item.active)
}
}
The code must not crash. If it does you made a design mistake
To use subscripts you first need to cast the object returned by
dic?firstObject
to a dictionary, you can also unwrap the optional at this point.
if let item = dic?firstObject as? [String: Any] {
print(item["name")
}

unable to access JSON array in swift

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.

Unable to Write Array of Dict to Plist File

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")
}

swift 3.1 reading CSV or PLIST file from web

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

Extension for Array in Swift to read an array from plist using NSPropertyListReadOptions

I am trying to implement an extension to the Array in Swift to be able to initialised from a plist file, I am trying to use NSPropertyListReadOptions
extension Array {
static func arrayFromPlistWithName (filename: String) -> [[String:AnyObject]]? {
let path = NSBundle.mainBundle().pathForResource(filename, ofType: "plist")!
let url = NSURL(fileURLWithPath: path)
let data = NSData(contentsOfURL: url)
let opts = NSPropertyListReadOptions(rawValue: NSPropertyListMutabilityOptions.MutableContainersAndLeaves.rawValue)
let plist = try! NSPropertyListSerialization.propertyListWithData(data!, options: opts, format: nil)
return plist as? [[String:AnyObject]]
}
}
and here how I use it:
let a:[[String:AnyObject]]? = Array.arrayFromPlistWithName("countries")
but I can an error :
Cannot convert value of type '[[String : AnyObject]]?' to specified type '[[String : AnyObject]]?'
My question is how to make it work and how also to handle all errors because I use a lot of force unwrapping which may be a cause of crashes
Try something like this:
enum ArrayFromPlistError: ErrorType {
case InvalidPlist
}
extension Array {
static func readArray(FromPlistNamed name: String) throws -> [AnyObject] {
let path = NSBundle.mainBundle().pathForResource(name, ofType: "plist")!
if let array = NSArray(contentsOfFile:path) {
return array as AnyObject as! [AnyObject]
}
else {
throw ArrayFromPlistError.InvalidPlist
}
}
}

Resources