Write and Read a Plist in Swift with Simple Data

Write and Read a plist in swift with simple data

Update for Swift 4

I have created SwiftyPlistManager. Take a look at it on GiHub and follow these video instructions:

YouTube Documentation

https://www.youtube.com/playlist?list=PL_csAAO9PQ8bKg79CX5PEfn886SMMDj3j

Update for Swift 3.1

let BedroomFloorKey = "BedroomFloor"
let BedroomWallKey = "BedroomWall"
var bedroomFloorID: Any = 101
var bedroomWallID: Any = 101

func loadGameData() {

// getting path to GameData.plist
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) as NSArray
let documentsDirectory = paths.object(at: 0) as! NSString
let path = documentsDirectory.appendingPathComponent("GameData.plist")

let fileManager = FileManager.default

//check if file exists
if !fileManager.fileExists(atPath: path) {

guard let bundlePath = Bundle.main.path(forResource: "GameData", ofType: "plist") else { return }

do {
try fileManager.copyItem(atPath: bundlePath, toPath: path)
} catch let error as NSError {
print("Unable to copy file. ERROR: \(error.localizedDescription)")
}
}

let resultDictionary = NSMutableDictionary(contentsOfFile: path)
print("Loaded GameData.plist file is --> \(resultDictionary?.description ?? "")")

let myDict = NSDictionary(contentsOfFile: path)

if let dict = myDict {
//loading values
bedroomFloorID = dict.object(forKey: BedroomFloorKey)!
bedroomWallID = dict.object(forKey: BedroomWallKey)!
//...
} else {
print("WARNING: Couldn't create dictionary from GameData.plist! Default values will be used!")
}
}

func saveGameData() {

let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) as NSArray
let documentsDirectory = paths.object(at: 0) as! NSString
let path = documentsDirectory.appendingPathComponent("GameData.plist")

let dict: NSMutableDictionary = ["XInitializerItem": "DoNotEverChangeMe"]
//saving values
dict.setObject(bedroomFloorID, forKey: BedroomFloorKey as NSCopying)
dict.setObject(bedroomWallID, forKey: BedroomWallKey as NSCopying)
//...

//writing to GameData.plist
dict.write(toFile: path, atomically: false)

let resultDictionary = NSMutableDictionary(contentsOfFile: path)
print("Saved GameData.plist file is --> \(resultDictionary?.description ?? "")")
}

Here's what I use to read/write a plist file in swift:

let BedroomFloorKey = "BedroomFloor"
let BedroomWallKey = "BedroomWall"
var bedroomFloorID: AnyObject = 101
var bedroomWallID: AnyObject = 101

func loadGameData() {

// getting path to GameData.plist
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) as NSArray
let documentsDirectory = paths[0] as String
let path = documentsDirectory.stringByAppendingPathComponent("GameData.plist")

let fileManager = NSFileManager.defaultManager()

//check if file exists
if(!fileManager.fileExistsAtPath(path)) {
// If it doesn't, copy it from the default file in the Bundle
if let bundlePath = NSBundle.mainBundle().pathForResource("GameData", ofType: "plist") {

let resultDictionary = NSMutableDictionary(contentsOfFile: bundlePath)
println("Bundle GameData.plist file is --> \(resultDictionary?.description)")

fileManager.copyItemAtPath(bundlePath, toPath: path, error: nil)
println("copy")
} else {
println("GameData.plist not found. Please, make sure it is part of the bundle.")
}
} else {
println("GameData.plist already exits at path.")
// use this to delete file from documents directory
//fileManager.removeItemAtPath(path, error: nil)
}

let resultDictionary = NSMutableDictionary(contentsOfFile: path)
println("Loaded GameData.plist file is --> \(resultDictionary?.description)")

var myDict = NSDictionary(contentsOfFile: path)

if let dict = myDict {
//loading values
bedroomFloorID = dict.objectForKey(BedroomFloorKey)!
bedroomWallID = dict.objectForKey(BedroomWallKey)!
//...
} else {
println("WARNING: Couldn't create dictionary from GameData.plist! Default values will be used!")
}
}

func saveGameData() {

let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) as NSArray
let documentsDirectory = paths.objectAtIndex(0) as NSString
let path = documentsDirectory.stringByAppendingPathComponent("GameData.plist")

var dict: NSMutableDictionary = ["XInitializerItem": "DoNotEverChangeMe"]
//saving values
dict.setObject(bedroomFloorID, forKey: BedroomFloorKey)
dict.setObject(bedroomWallID, forKey: BedroomWallKey)
//...

//writing to GameData.plist
dict.writeToFile(path, atomically: false)

let resultDictionary = NSMutableDictionary(contentsOfFile: path)
println("Saved GameData.plist file is --> \(resultDictionary?.description)")
}

The plist file is this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BedroomFloor</key>
<integer>101</integer>
<key>BedroomWall</key>
<integer>101</integer>
<key>XInitializerItem</key>
<string>DoNotEverChangeMe</string>
</dict>
</plist>

How can I read data from a PLIST in Swift?

First, your Root object in the plist is an NSArray not a NSDictionary.

Second, if you want to use KVC on Foundation Collections (I don't believe this works with Swift's Array) you need to call valueForKeyPath.

let chapterPath = NSBundle.mainBundle().pathForResource("chapterMapping", ofType: "plist")
if let arrayOfItems: [AnyObject] = NSArray(contentsOfFile: chapterPath!) {
let chapterNames: [String] = arrayOfItems.valueForKeyPath("chapterName") as NSArray as [String]
let pageNumbers: [Int] = arrayOfItems.valueForKeyPath("pageNumber") as NSArray as [Int]
}

Third, the swift-y way of doing this would be with the map function, but arrayOfItems would need to be a strongly-defined type and it might be more work than it's worth. Example:

let array: [ChapterMetaData] = // define it here
let chapterImages = array.map { $0.chapterImage }

Swift 5: How to read variables in plist files?

Your property list format is not very convenient. As you want an array anyway create the property list with an array for key animals (and name all keys lowercased)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>animals</key>
<array>
<dict>
<key>name</key>
<string>Tiger</string>
<key>picture</key>
<string>tiger_running</string>
</dict>
<dict>
<key>name</key>
<string>Jaguar</string>
<key>picture</key>
<string>jaguar_sleeping</string>
</dict>
</array>
</dict>
</plist>

Then create two structs

struct Root : Decodable {
let animals : [Animal]
}

struct Animal : Decodable {
let name, picture : String
}

and the data source array

var animals = [Animal]()

And decode the stuff

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let url = Bundle.main.url(forResource: "Animals", withExtension:"plist")!
do {
let data = try Data(contentsOf: url)
let result = try PropertyListDecoder().decode(Root.self, from: data)
self.animals = result.animals
} catch { print(error) }
}

PropertyListDecoder and PropertyListSerialization are state of the art in Swift. The NSDictionary/NSArray API is objective-c-ish and outdated.

Save Data to .plist File in Swift

Apparently the file is not in a writable location, so I created it in the documents directory.

var paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as String
var path = paths.stringByAppendingPathComponent("data.plist")
var fileManager = NSFileManager.defaultManager()
if (!(fileManager.fileExistsAtPath(path)))
{
var bundle : NSString = NSBundle.mainBundle().pathForResource("data", ofType: "plist")
fileManager.copyItemAtPath(bundle, toPath: path, error:nil)
}
data.setObject(object, forKey: "object")
data.writeToFile(path, atomically: true)

Then, it has to be read from the documents directory.

var paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as String
var path = paths.stringByAppendingPathComponent("data.plist")
let save = NSDictionary(contentsOfFile: path)

How to read from a plist with Swift 3 iOS app

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

read/write to array plist with Swift

Your problem is that you are loading the plist into your documents directory in loadDataPlist(), but you are still pulling the plist from it's original location in your writeToPlist(indexValue:) function. So you are trying to write to a plist at a readonly location. Change your write function to this:

func writeToPlist(indexValue:Int) {
var paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as String
var path = paths.stringByAppendingPathComponent("MyPlist.plist")

if var plistArray = NSMutableArray(contentsOfFile: path) {
plistArray.addObject(indexValue)
plistArray.addObject(indexValue+5)
plistArray.addObject(indexValue+10)
plistArray.addObject(indexValue+15)
plistArray.writeToFile(path, atomically: false)
}
}

Note that instead of adding the values to myArray I'm only writing to the plist. You'll have to decide if you want to maintain the two arrays (the local variable myArray and the array in the plist) depending on what you're using this for. Either way, this should fix your problem.



Related Topics



Leave a reply



Submit