Write to plist file in Swift
Asked Answered
V

2

8

I have a sample plist-file, favCities.plist. This is my sample code:

 override func viewDidLoad() {
    super.viewDidLoad()


    let path = NSBundle.mainBundle().pathForResource("favCities", ofType: "plist")
    var plistArray = NSArray(contentsOfFile: path) as [Dictionary<String, String>]

    var addDic: Dictionary = ["ZMV": "TEST", "Name": "TEST", "Country": "TEST"]
    plistArray += addDic
    (plistArray as NSArray).writeToFile(path, atomically: true)

    var plistArray2 = NSArray(contentsOfFile: path)

    for tempDict1 in plistArray2 {
        var tempDict2: NSDictionary = tempDict1 as NSDictionary
        var cityName: String = tempDict2.valueForKey("Name") as String
        var cityZMV: String = tempDict2.valueForKey("ZMV") as String
        var cityCountry: String = tempDict2.valueForKey("Country") as String
        println("City: \(cityName), ZMV: \(cityZMV), Country: \(cityCountry)")
    }

At first glance, everything works well. The output looks like this:

City: Moscow, ZMV: 00000.1.27612, Country: RU
City: New York, ZMV: 10001.5.99999, Country: US
City: TEST, ZMV: TEST, Country: TEST

But when I interrupt the app, I see that my file favCities.plist has not changed. There are still two values. These values ​​- City: TEST, ZMV: TEST, Country: TEST - were not added. If I restarted the application, then again I see 3 lines of output, although there should be 4.

What is wrong?

UPDATED:

I was changed code to this:

override func viewDidLoad() {
    super.viewDidLoad()

    let fileManager = (NSFileManager.defaultManager())
    let directorys : [String]? = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory,NSSearchPathDomainMask.AllDomainsMask, true) as? [String]

    if (directorys! != nil){
        let directories:[String] = directorys!;
        let dictionary = directories[0];

        let plistfile = "favCities.plist"
        let plistpath = dictionary.stringByAppendingPathComponent(plistfile);

        println("\(plistpath)")

        var plistArray = NSArray(contentsOfFile: plistpath) as [Dictionary<String, String>]

        var addDic: Dictionary = ["ZMV": "TEST", "Name": "TEST", "Country": "TEST"]
        plistArray += addDic

        (plistArray as NSArray).writeToFile(plistpath, atomically: false)

        var plistArray2 = NSArray(contentsOfFile: plistpath)

        for tempDict1 in plistArray2 {
            var tempDict2: NSDictionary = tempDict1 as NSDictionary
            var cityName: String = tempDict2.valueForKey("Name") as String
            var cityZMV: String = tempDict2.valueForKey("ZMV") as String
            var cityCountry: String = tempDict2.valueForKey("Country") as String
            println("City: \(cityName), ZMV: \(cityZMV), Country: \(cityCountry)")
        }
    }
    else {
        println("ERROR!")
    }

}

Now when you run the application the number of rows in the output increases:

City: Moscow, ZMV: 00000.1.27612, Country: RU
City: New York, ZMV: 10001.5.99999, Country: US
City: TEST, ZMV: TEST, Country: TEST
City: TEST, ZMV: TEST, Country: TEST
........

BUT! If view the file favCities.plist, which is located in the project folder (Project Navigator in Xcode), it still remains unchanged - there are two lines!

If walk along the path, which is stored in the variable plistpath - /Users/admin/Library/Developer/CoreSimulator/Devices/55FD9B7F-78F6-47E2-9874-AF30A21CD4A6/data/Containers/Data/Application/DEE6C3C8-6A44-4255-9A87-2CEF6082A63A/Documents/

Then there is one more file favCities.plist. It contains all the changes that make the application. What am I doing wrong? How can I see all the changes in a file that is located in the project folder (Project Navigator)?

Veto answered 11/7, 2014 at 7:55 Comment(4)
check return value of writeToFile, and you will see it failed. because files in main bundle are readonlyPorphyritic
possible duplicate of writing NSDictionary to plist in my app bundleChasse
(So all typical errors in iOS applications that were once made using Objective-C, are now made again using Swift :-)Chasse
Or plist-files in the project folder needs only for initial use (like properties, etc.)?Veto
R
0

Mostly, people want to store a list of something, so here is my share on how to do this, also, here I don't copy the plist file, I just create it. The actual saving/loading is quite similar to the answer from Rebeloper

xcode 7 beta, Swift 2.0

saving

func SaveItemFavorites(items : Array<ItemFavorite>) -> Bool
{
    let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) as NSArray
    let docuDir = paths.firstObject as! String
    let path = docuDir.stringByAppendingPathComponent(ItemFavoritesFilePath)
    let filemanager = NSFileManager.defaultManager()

    let array = NSMutableArray()        
    
    for var i = 0 ; i < items.count ; i++
    {
        let dict = NSMutableDictionary()
        let ItemCode = items[i].ItemCode as NSString
        
        dict.setObject(ItemCode, forKey: "ItemCode")
        //add any aditional..
        array[i] = dict
    }
    
    let favoritesDictionary = NSDictionary(object: array, forKey: "favorites")
    //check if file exists
    if(!filemanager.fileExistsAtPath(path))
    {
        let created = filemanager.createFileAtPath(path, contents: nil, attributes: nil)
         if(created)
         {
             let succeeded = favoritesDictionary.writeToFile(path, atomically: true)
             return succeeded
         }
         return false
    }
    else
    {
        let succeeded = notificationDictionary.writeToFile(path, atomically: true)
        return succeeded
    }
}

Little note from the docs:

NSDictionary.writeToFile(path:atomically:)

This method recursively validates that all the contained objects are property list objects (instances of NSData, NSDate, NSNumber, NSString, NSArray, or NSDictionary) before writing out the file, and returns NO if all the objects are not property list objects, since the resultant file would not be a valid property list.

So whatever you set at dict.SetObject() should be one of the above mentioned types.

loading

private let ItemFavoritesFilePath = "ItemFavorites.plist"

func LoadItemFavorites() -> Array<ItemFavorite>
{
    let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) as NSArray
    let docuDir = paths.firstObject as! String
    let path = docuDir.stringByAppendingPathComponent(ItemFavoritesFilePath)
    let dict = NSDictionary(contentsOfFile: path)
    let dictitems : AnyObject? = dict?.objectForKey("favorites")
    
    var favoriteItemsList = Array<ItemFavorite>()
    
    if let arrayitems = dictitems as? NSArray
    {
        for var i = 0;i<arrayitems.count;i++
        {
            if let itemDict = arrayitems[i] as? NSDictionary
            {
                let ItemCode = itemDict.objectForKey("ItemCode") as? String
                //get any additional
                
                let ItemFavorite = ItemFavorite(item: ItemCode)
                favoriteItemsList.append(ItemFavorite)
            }
        }
    }
    
    return favoriteItemsList
}
Raquelraquela answered 13/7, 2015 at 18:26 Comment(0)
N
0

Apart from the fact that the application bundle is read-only (for obvious reasons), since Swift 4 there is PropertyListDecoder/Encoder to read and write property lists without the bridged Objective-C APIs.

First create a struct for the model conforming to Codable

struct FavCity : Codable {
    let city, zmv, country: String
}

Then specify two URLs, the url of the default file in the bundle and one URL in the documents directory to be able to modify the file

let fileManager = FileManager.default

let applicationBundleFileURL = Bundle.main.url(forResource: "favCities", 
                                               withExtension: "plist")!
let documentsFileURL = try! fileManager.url(for: .documentDirectory,
                                            in: .userDomainMask,
                                            appropriateFor: nil,
                                            create: false)
    .appendingPathComponent("favCities.plist")

the try! doesn't matter because the system makes sure that the folder Documents exists.

Create a new favorite city

let newCity = FavCity(city: "TEST", zmv: "TEST", country: "TEST")

Now read the file in the documents directory. If it doesn't exist read the file in the bundle. Finally append the new city and write the property list data back to the documents directory

let data : Data
do {
    data = try Data(contentsOf: documentsFileURL)
} catch {
    data = try! Data(contentsOf: applicationBundleFileURL)
}
do {
    var favCities = try PropertyListDecoder().decode([FavCity].self, from: data)
    favCities.append(newCity)
    let newData = try PropertyListEncoder().encode(favCities)
    try newData.write(to: documentsFileURL)
} catch {
    print(error)
}
Nunes answered 18/4, 2022 at 12:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.