Plist

Reading Time: 8 minutes

Reading a Plist with Swift

Property lists are perfect for saving simple, static key-value data in your app. Here’s how you can read a plist with Swift:

func getPlist(withName name: String) -> [String]?
{
    if  let path = Bundle.main.path(forResource: name, ofType: "plist"),
        let xml = FileManager.default.contents(atPath: path)
    {
        return (try? PropertyListSerialization.propertyList(from: xml, options: .mutableContainersAndLeaves, format: nil)) as? [String]
    }

    return nil
}

Here’s how we can use the above function:

if let fruits = getPlist(withName: "Fruit") {
    print(fruits) // Output: ["Orange", "Pineapple", "Raspberry", ···]
}

What happens in the code?

  • First, we’re using optional binding to get the path of the Fruit.plist file, and read the data from that file into the xml constant. If any of these two lines fails, path or xml are nil, and the conditional won’t execute.
  • Then, inside the conditional, we’re deserializing the data from xml as a property list. The property list is returned as an array, but we’ll have to cast it to [String] because propertyList(from:options:format:) returns Any.
  • When the conditional fails, or the type casting fails, or the propertyList(from:options:format:) call throws an error, the function returns nil. Otherwise it returns an array of type [String]?, so when we call getPlist(withName:) we’re using optional binding again to get the non-optional array fruits.

It’s important to point out that the type of fruit, and the return type of getPlist(withName:) is the same type as the data in our plist. The plist contains an array of strings, so the type of fruit is also [String]. You’ll always want to use the strictest possible type, such as [StringAny].

Warning: For the sake of simplicitly, the above code has no error handling. It’ll simply silence errors and return nil. This is a bad practice in production apps. You’ll want to include error handling code in case the file path doesn’t exist, or if the plist is invalid and an error is thrown.

Reading a Plist with Codable

The previous approach to read a property list is far from ideal. It’s too verbose and doesn’t use strict type checking. Any change in the plist structure could potentially break your code.

Since iOS 11 you can use the Codable protocol to directly decode different data types, such as JSON, to native Swift classes and structs. And you can also use Codable with plists!

It contains a few simple user preferences for our app, similar to what you’d put in UserDefaults.

We now need a Preferences struct, that conforms to the Codable protocol. Like this:

struct Preferences: Codable {
    var webserviceURL:String
    var itemsPerPage:Int
    var backupEnabled:Bool
}

It’s important that the structs properties exactly match the keys in the plist dictionary. And note that we’re adopting the Codable protocol.

The Preferences struct contains some arbitrary information about backups, about a webservice URL, and how many items we want to show per page. You can put anything in your own plist, of course.

What we’ll now do is turn the Preferences.plist file into an instance of the Preferences struct. That’s the power of Codable! Here’s how:

if  let path        = Bundle.main.path(forResource: "Preferences", ofType: "plist"),
    let xml         = FileManager.default.contents(atPath: path),
    let preferences = try? PropertyListDecoder().decode(Preferences.self, from: xml)
{
    print(preferences.webserviceURL)
}

We’re using the same boilerplate code as before. First, we’re getting the path of the plist, and then getting the Data from the file, and assigning that to xml.

This line is the most relevant:

let preferences = try? PropertyListDecoder().decode(Preferences.self, from: xml)

By calling the decode(_:from:) function on an instance of PropertyListDecoder(), the property list file gets decoded to an instance of Preferences. We’re instructing the decoder to use that struct, with the first argument Preferences.self. Decoding and encoding a property list uses the exact same syntax as decoding/encoding JSON, but it uses a different decoder instance.

When the decoding succeeds, we can use the preferences object as any other ordinary Swift type:

print(preferences.webserviceURL)

Awesome!

Important: Again, we’re silencing any errors. Make sure you properly handle errors in your own apps.

Writing Data to a Plist

Can you also write a dictionary or array back to a property list file? Of course! Let’s say that the user doesn’t have a Preferences property list yet, so we’ll create a default for them. Here’s how:

let preferences = Preferences(webserviceURL: "https://api.twitter.com", itemsPerPage: 10, backupEnabled: false)

With the above code we’re creating a new instance of Preferences that’s not yet stored as a property list. And here’s how we save it:

let encoder = PropertyListEncoder()
encoder.outputFormat = .xml

let path = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("Preferences.plist")

do {
    let data = try encoder.encode(preferences)
    try data.write(to: path)
} catch {
    print(error)
}

This is what happens in the above code:

  • First, we’re creating a PropertyListEncoder object and setting the output format to .xml. (Other options are .binary and the ancient .openStep.)
  • Then, we’re creating a URL object with a reference to a Preferences.plist file in the app’s document directory. This is a directory in the app’s container that we’re allowed to put files in.
  • Then, in the do-try-catch block, we’re first using the encoder to encode the preferences object to a Data object. After that, we simply write the data to the file referenced by path.

Easy-peasy! The encoder turns the Preferences object into data that uses the XML property list format, and writes that to a file. And later on, we can read that same file URL to get the Preferences object back again.

The iOS SDK has a few helpful component for encoding and decoding data formats, such as PropertyListEncoderNSKeyedArchiverNSCoding and JSONEncoder. They’re worth checking out!

Info.plist

A configuration file in a strict text format (a property list file). It is derived from, but is not identical to, the project’s Info.plist. It contains instructions to the system about how to treat and launch the app. For example, the project’s Info.plist has a calculated bundle name derived from the product name, $(PRODUCT_NAME); in the built app’s Info.plist, this calculation has been performed, and the value reads Empty Window, which is why our app is labeled “Empty Window” on the device. Also, in conjunction with the asset catalog writing out our icon file to the app bundle’s top level, a setting has been added to the built app’s Info.plist telling the system the name of that icon file, so that the system can find it and display it as our app’s icon.