persist dictionary to file and read it back - ios

I am learning Swift. I have created an dictionary:
var myDict = Dictionary<String, String>()
myDict["AAA"] = "aaa"
myDict["BBB"] = "bbb"
Now I want to persist this dictionary to a file. I get a file path under documents:
let docDir = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).last as? String
let filePath = docDir.stringByAppendingPathComponent("MyFile.txt")
Until here, I don't know how to store myDict to the file. And also how to read the myDict back from file. Could someone please guide me through please?

You can use NSMutableDictionary
private var myDict: NSMutableDictionary!
For writing to user defaults:
let def:NSUserDefaults = NSUserDefaults.standardUserDefaults();
def.setObject(myDict, forKey: "YOUR_KEY");
def.synchronize();
For writing to file:
var text:String = ""
for (key, value) in myDict {
text += "\(key as! String)=\(value as! String)\n"
}
do {
try text.writeToFile(path, atomically: false, encoding: NSUTF8StringEncoding)
}
catch {/* error handling here */}
For reading:
let def:NSUserDefaults = NSUserDefaults.standardUserDefaults();
let ur:NSDictionary? = def.objectForKey("YOUR_KEY") as? NSDictionary;
if(ur == nil) {
myDict = NSMutableDictionary();
myDict.setValue("aaa", forKey:"AAA");
myDict.setValue("bbb", forKey:"BBB");
}
else{
myDict = NSMutableDictionary(dictionary: ur!)
}

Related

Could not cast value of type 'Swift._SwiftDeferredNSDictionary<Swift.String, Swift.String>' to 'NSMutableDictionary'

I have an App written in Swift 3.0 and I declared the following data types:
var movies = [Movie]()
var getPlist = NSMutableDictionary()
var movieItems = NSMutableDictionary()
And I have the following method which is loading the content of a plist:
// Connect to plist and get the data
if let plist = PlistHandler(name: "MovieData") {
getPlist = plist.getMutablePlistDict()!
// Load the movie items into the table view data source
for i in 0..<getPlist.count {
movieItems = (getPlist.object(forKey: "Item\(i)") as! NSMutableDictionary) as! [String: String] as! NSMutableDictionary
let newName = movieItems.object(forKey: "Name")
let newRemark = movieItems.object(forKey: "Remark")
if newName as? String != "" {
movies.append(Movie(name: newName as? String, remark: newRemark as? String)
)}
}
} else {
print("Unable to get Plist")
}
It calls a method called getMutablePlistDict() from another class:
// Get the values from plist -> MutableDirectory
func getMutablePlistDict() -> NSMutableDictionary? {
let fileManager = FileManager.default
if fileManager.fileExists(atPath: destPath!) {
guard let dict = NSMutableDictionary(contentsOfFile: destPath!) else { return .none }
return dict
} else {
return .none
}
}
When I run the App I get the error above (see question title). But this is new. In Xcode 8 I didn't get this error. What is the reason for this and how I have to change my code to avoid that?
You can use like this :
Changed NSMutableDictionary to [String: Any] :
var movies = [Movie]()
var getPlist: [String: Any] = [:]
var movieItems: [String: Any] = [:]
func getMutablePlistDict() -> [String: Any] {
let fileManager = FileManager.default
if fileManager.fileExists(atPath: destPath!) {
if let dict = NSDictionary(contentsOfFile: destPath!) as? [String: Any] {
return dict
}
} else {
return [:]
}
}
if let plist = PlistHandler(name: "MovieData") {
let getPlist = plist.getMutablePlistDict()
// Load the movie items into the table view data source
for i in 0..<getPlist.count {
if let movieItemsCheck = getPlist["Item\(i)"] as? [String: Any] {
movieItems = movieItemsCheck
if let newName = movieItems["Name"] as? String, let newRemark = movieItems["Remark"] as? String, newName != "" {
movies.append(Movie(name: newName, remark: newRemark))
}
}
}
} else {
print("Unable to get Plist")
}

How to read from a plist with Swift 3 iOS app

-Disclaimer-
I'm extremely new to iOS and Swift development, but I'm not particularly new to programming.
I have a basic iOS application with Swift3 elements in it.I've created a plist file with some entries I want to read and display in my application. (No write access is necessary)
How can you read a value for a given key for a bundled plist file, in Swift3?
This seems like a really simple question to me, but a bunch of searching is making me question my whole conceptual approach.
Helpful tips would be appreciated.
Same way you have done in Swift 2.3 or lower just syntax is changed.
if let path = Bundle.main.path(forResource: "fileName", ofType: "plist") {
//If your plist contain root as Array
if let array = NSArray(contentsOfFile: path) as? [[String: Any]] {
}
////If your plist contain root as Dictionary
if let dic = NSDictionary(contentsOfFile: path) as? [String: Any] {
}
}
Note: In Swift it is better to use Swift's generic type Array and Dictionary instead of NSArray and NSDictionary.
Edit: Instead of NSArray(contentsOfFile: path) and NSDictionary(contentsOfFile:) we can also use PropertyListSerialization.propertyList(from:) to read data from plist file.
if let fileUrl = Bundle.main.url(forResource: "fileName", withExtension: "plist"),
let data = try? Data(contentsOf: fileUrl) {
if let result = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [[String: Any]] { // [String: Any] which ever it is
print(result)
}
}
As Swift 4 introduces Codable
Step 1: Load the Plist File from bundle.
Step 2: Use PropertyListDecoder for the decoding of property list values into semantic Decodable types.
Step 3: Create Codable Struct
Complete code -
func setData() {
// location of plist file
if let settingsURL = Bundle.main.path(forResource: "JsonPlist", ofType: "plist") {
do {
var settings: MySettings?
let data = try Data(contentsOf: URL(fileURLWithPath: settingsURL))
let decoder = PropertyListDecoder()
settings = try decoder.decode(MySettings.self, from: data)
print("toolString is \(settings?.toolString ?? "")")
print("DeviceDictionary is \(settings?.deviceDictionary?.phone ?? "")")
print("RootPartArray is \(settings?.RootPartArray ?? [""])")
} catch {
print(error)
}
}
}
}
struct MySettings: Codable {
var toolString: String?
var deviceDictionary: DeviceDictionary?
var RootPartArray: [String]?
private enum CodingKeys: String, CodingKey {
case toolString = "ToolString"
case deviceDictionary = "DeviceDictionary"
case RootPartArray
}
struct DeviceDictionary: Codable {
var phone: String?
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
phone = try values.decodeIfPresent(String.self, forKey: .phone)
}
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
toolString = try values.decodeIfPresent(String.self, forKey: .toolString)
deviceDictionary = try values.decodeIfPresent(DeviceDictionary.self, forKey: .deviceDictionary)
RootPartArray = try values.decodeIfPresent([String].self, forKey: .RootPartArray)
}
}
Sample Plist file -> https://gist.github.com/janeshsutharios/4b0fb0e3edeff961d3e1f2829eb518db
Here is example how to get BundleID from Info plist:
var appBundleID = "Unknown Bundle ID"
if let bundleDict = Bundle.main.infoDictionary,
let bundleID = bundleDict[kCFBundleIdentifierKey as String] as? String {
appBundleID = bundleID
}
The same way you may easily access any key. This approach is good for many-target projects.
Here is a Swift 3 implementation, based on Nirav D's answer:
/// Read Plist File.
///
/// - Parameter fileURL: file URL.
/// - Returns: return plist content.
func ReadPlist(_ fileURL: URL) -> [String: Any]? {
guard fileURL.pathExtension == FileExtension.plist, let data = try? Data(contentsOf: fileURL) else {
return nil
}
guard let result = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: Any] else {
return nil
}
print(result)
return result
}
For Swift 3.0, Following code directly targeting to key. Where as dict object will give everything which will be there in your plist file.
if let path = Bundle.main.path(forResource: "YourPlistFile", ofType: "plist"), let dict = NSDictionary(contentsOfFile: path) as? [String: AnyObject] {
let value = dict["KeyInYourPlistFile"] as! String
}
In AppDelegate File
var bundlePath:String!
var documentPath:String!
var plistDocumentPath:URL!
let fileManager = FileManager()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
{
bundlePath = Bundle.main.path(forResource: "Team", ofType: "plist")
documentPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first
plistDocumentPath = URL.init(string: documentPath)?.appendingPathComponent("Team.plist")
print(plistDocumentPath.path)
if !fileManager.fileExists(atPath: plistDocumentPath.path){
do {
try fileManager.copyItem(atPath: bundlePath, toPath: plistDocumentPath.path)
} catch {
print("error Occured \(error.localizedDescription)")
}
}
return true
}
In ViewController
#IBOutlet weak var TeamTable: UITableView!
var appDelegate:AppDelegate!
var arrayForContacts:[[String:Any]]! // array object
override func viewDidLoad() {
super.viewDidLoad()
appDelegate = UIApplication.shared.delegate as! AppDelegate
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if appDelegate.fileManager.fileExists(atPath: appDelegate.plistDocumentPath.path){
arrayForContacts = []
if let contentOfPlist = NSArray.init(contentsOfFile: appDelegate.plistDocumentPath.path ){
arrayForContacts = contentOfPlist as! [[String:Any]]
TeamTable.reloadData()
}
}
}
You can also read value directly from your plist file by simply
let value = Bundle.init(for: AppDelegate.self).infoDictionary?["your plist key name"] as? Any

How to access a property list

I have created a property list with continents, countries, and random facts as shown below:
I can access the top level keys from the property list easily enough:
if let path = NSBundle.mainBundle().pathForResource("countryData", ofType: "plist") {
dict = NSDictionary(contentsOfFile: path)
}
countries += dict!.allKeys as! [String]
If I wanted to access the second element in the vanuatu array, however, things fall apart. I would think objectForKey would get the country dictionary and then use objectForKey again to get the country array. But so far, that hasn't worked. At all...
if let path = NSBundle.mainBundle().pathForResource("countryData", ofType: "plist") {
dict = NSDictionary(contentsOfFile: path)
if let australia = dict["australia"] as? [String:AnyObject]{
// access the second element's property here
if let vanuatu = australia["vanuatu"] as? [String]{
// Access the vanuatu here
}
}
}
if let path = NSBundle.mainBundle().pathForResource("Property List", ofType: "plist") {
dict = NSDictionary(contentsOfFile: path)
if let vanuatu = dict.objectForKey("australia") as? [String:AnyObject]{
if let vanuatuArray = vanuatu["vanuatu"] as? [String]{
print(vanuatuArray[1])
}
}
}
You can get data from plist file like that.
I have created a plist file for countryCodes.
func fetchCounrtyCodes() -> [CountryCodes]{
let name = "name"
let dial_code = "dial_code"
let code = "code"
var countryArray = [CountryCodes]()
guard let filePath = NSBundle.mainBundle().pathForResource("CountryList", ofType: "json") else {
print("File doesnot exist")
return []
}
guard let jsonData = NSData(contentsOfFile: filePath) else {
print("error parsing data from file")
return []
}
do {
guard let jsonArray = try NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.AllowFragments) as? [[String:String]] else {
print("json doesnot confirm to expected format")
return []
}
countryArray = jsonArray.map({ (object) -> CountryCodes in
return CountryCodes(name: object[name]!, dial_code:object[dial_code]!, code: object[code]!)
})
}
catch {
print("error\(error)")
}
return countryArray
}
struct CountryCodes{
var name = ""
var dial_code = ""
var code = ""
}

Json parsing in iOS playground do method is not parsing

I am trying to parse some json data into three different arrays based off the label in the json. I seem to be stuck and don't know why my for loop is never being entered. I am new to iOS and am using this to learn swift. Any help will be appreciated.
Here is the code that I am using:
var myPicture = [String]()
var myPath = [String]()
var mylabel = [String]()
let jsonString = "[{\"picture\" : \"Picture 1 \", \"path\": \"Path 1\" , \"label\" : \"Label 1\"}]"
//Convert jsonString to NSData
let myData = jsonString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
do{
let promoJson = try NSJSONSerialization.JSONObjectWithData(myData, options:.AllowFragments)
if let promtions = promoJson[""] as? [[String: AnyObject]] {
for promtions in promtions {
if let picture = promtions["picture"] as? String
{
myPicture.append(picture)
if let path = promtions["path"] as? String
{
myPath.append(path)
if let label = promtions["label"] as? String
{
mylabel.append(label)
}
}
}
}
}
}catch {
print("Error with Json: \(error)")
}
print(myPicture.first)
print(myPath.first)
print(mylabel.first)
The results for the print are all nil. So nothing is being appended to the arrays
The if let promtions = promoJson[""] part won't work and would be useless anyway. This is only promoJson that you have to cast to an array of dictionaries.
You weren't that far from the solution, look at my working version of your code:
do {
let promoJson = try NSJSONSerialization.JSONObjectWithData(myData, options: [])
if let promtions = promoJson as? [[String: AnyObject]] {
for promtion in promtions {
if let picture = promtion["picture"] as? String {
myPicture.append(picture)
}
if let path = promtion["path"] as? String {
myPath.append(path)
}
if let label = promtion["label"] as? String {
mylabel.append(label)
}
}
}
} catch let error as NSError {
print(error.debugDescription)
}
Alternative
Now that the issue is resolved, let me suggest you another way: instead of separate arrays for your data, use one array of objects holding your data.
For example, make a struct like this:
struct Promotion {
let picture: String
let path: String
let label: String
}
And an array for instances of this struct:
var myPromotions = [Promotion]()
Now we can decode the JSON, create objects from it then store them in the array:
do {
let promoJson = try NSJSONSerialization.JSONObjectWithData(myData, options: [])
if let promtions = promoJson as? [[String: AnyObject]] {
for promtion in promtions {
if let picture = promtion["picture"] as? String,
path = promtion["path"] as? String,
label = promtion["label"] as? String {
let promo = Promotion(picture: picture, path: path, label: label)
myPromotions.append(promo)
}
}
}
} catch let error as NSError {
print(error.debugDescription)
}
Now look at the content of the array, very convenient:
for promo in myPromotions {
print(promo.label)
print(promo.path)
print(promo.picture)
}
When you are converting it is already an array.
import Foundation
import UIKit
var myPicture = [String]()
var myPath = [String]()
var mylabel = [String]()
let jsonString = "[{\"picture\" : \"Picture 1 \", \"path\": \"Path 1\" , \"label\" : \"Label 1\"}]"
//Convert jsonString to NSData
let myData = jsonString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
do{
let promoJson = try NSJSONSerialization.JSONObjectWithData(myData, options:.AllowFragments) as! NSArray
for promtions in promoJson {
if let picture = promtions["picture"] as? String
{
myPicture.append(picture)
if let path = promtions["path"] as? String
{
myPath.append(path)
if let label = promtions["label"] as? String
{
mylabel.append(label)
}
}
}
}
}catch
{
print("Error with Json: \(error)")
}
print(myPicture.first) // "Optional("Picture 1 ")\n"
print(myPath.first) // "Optional("Path 1")\n"
print(mylabel.first) // "Optional("Label 1")\n"
This does the job.

why I cannot read data from plist?

This is part my code. But myDict is nil. The filename is right.I already checked for many times.
var myDict: NSDictionary?
if let path = NSBundle.mainBundle().pathForResource("CatData", ofType: "plist") {
myDict = NSDictionary(contentsOfFile: path)
}
if let dict = myDict {
print("print something")
}
your plist is array of dictionary so try
var myArray: NSArray?
if let path = NSBundle.mainBundle().pathForResource("Categories", ofType: "plist") {
myArray = NSArray(contentsOfFile: path)
}
if let array = myArray {
for item: AnyObject in array {
if let item = item as? NSDictionary {
if let categoryTitle = item["CategoryTitle"] as? NSString {
print("categoryTitle = ", categoryTitle)
}
if let imageNames = item["ImageNames"] as? NSArray {
print("imageNames = ", imageNames)
}
}
}
}
Providing your file name is correct, and your plist has been added to the bundle:
You need to make sure your file is the type you're expecting.
For example. The root of your plist is an Array, and you're looking to get a Dictionary out of it.
So lets rewrite your code:
var myArray: Array? {
if let path = NSBundle.mainBundle().pathForResource("CatData", ofType: "plist") {
myArray = NSArray(contentsOfFile: path) as! [[String:AnyObject]]
}
if let array = myArray {
print("print something")
}
}

Resources