How To: Working With Plist In Swift

Written by Reinder de Vries on November 6 2018 in App Development

How To: Working With Plist In Swift

A property list, commonly abbreviated as plist, is an XML file that contains basic key-value data. You can use a plist in your iOS apps as a simple key-value data store. Let’s find out how that works!

In this article we’ll dive into:

  • What a property list is, and how you can use it
  • How to read from and write to a property list
  • What Info.plist is, and why it’s useful
  • How to encode and decode property lists with Codable

Ready? Let’s go.

  1. What’s A Plist?
  2. Reading A Plist With Swift
  3. Reading A Plist With Codable
  4. Writing Data To A Plist
  5. Further Reading

What’s A Plist?

A property list, or plist, is an XML file that contains key-value data. It’s easiest to compare with a dictionary in Swift, so it’s a list of values associated with keys. Here’s an example:

In the above screenshot you see a property list file called Fruit.plist. It has a top-level array, with a few items of type String. Here’s the same file, but as XML:

<?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">
<array>
    <string>Orange</string>
    <string>Pineapple</string>
    <string>Raspberry</string>
    <string>Banana</string>
    <string>Pear</string>
    <string>Apple</string>
</array>
</plist>

In Xcode you can see a plist’s XML structure by right-clicking on a .plist file, and choosing Open As -> Source Code.

If you look closely, you can spot the different values and structures in the XML. Here’s a brief overview from top to bottom:

  • The <?xml>, <!DOCTYPE> and <plist> lines are boilerplate code that give information about the XML itself. A typical XML file has a so-called DTD, which contains information about the structure of XML.
  • The top level <array> tag wraps the array items in it. Every XML element has an opening, with the <tagname> format, and closing tag, with the </tagname> format.
  • Inside the array you see 6 <string> tags. The array contains a few fruit names as strings.

The top level element of a plist, called the root, is always an array or a dictionary.

A property list can contain a few types of elements, including arrays, dictionaries, booleans, dates, Data, and numbers, such as integers. Every one of these types correspond to native Swift types, such as Array and Bool. You can also nest arrays and dictionaries, i.e. adding an array inside another array etc., to create more complex data structures.

Dictionary key-value pairs in plists aren’t wrapped in a container element. Instead, they’re written on two lines. Like this:

You don’t have to edit a property list as XML. That’s what’s so convenient about them! Simply open them with the property list editor, with Open As -> Property List, and directly edit the rows.

A very common property list in iOS projects is the Info.plist file. This plist contains information about your iOS project. It’s distributed as part of your app’s binary package, and iOS uses it for example to display your app’s name on an iPhone’s home screen.

See how there’s a ton of information about your app in Info.plist? As you build your app, you sometimes have to add more information to this property list. For example, if your app wants to access an iPhones photo library, the app plist needs to contain a key that explains the reason for accessing the photo library.

You can edit a property list as follows:

  • To add a new row, right-click on the editor and choose Add Row
  • To change a value’s type, click on the select button for a row, in the Type column.
  • To change a row’s value, double-click on a value for a row, in the Value column.
  • To remove a row, select it and press the Backspace key.

Some property lists will use a custom format, such as the Info.plist. When a property list has a custom format, you can’t just add any value to them. For instance, the Info.plist file can only contain dictionary elements with preset keys.

Let’s move on to find out how you can use property lists with Swift.

Learn how to build iOS apps

Get started with iOS 12 and Swift 5

Sign up for our iOS development course Zero to App Store and learn how to build professional iOS 12 apps with Swift 5 and Xcode 10.

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
}

And here’s how we can use that function:

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

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 [String: Any].

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!

Here’s the Preferences.plist we’ll use:

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 holds 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.

The next 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.

Learn how to build iOS apps

Get started with iOS 12 and Swift 5

Sign up for our iOS development course Zero to App Store and learn how to build professional iOS 12 apps with Swift 5 and Xcode 10.

Further Reading

And that’s all there is to it! Property lists are useful for reading and writing user settings to a file, and they’re easy to edit in Xcode.

Quick Tip: MacOS has a command-line tool called plutil that you can use to edit property lists. Type plutil -help in Terminal to read how you can use it.

Want to learn more? Check out these resources:

Reinder de Vries

Reinder de Vries

Reinder de Vries is a professional iOS developer. He teaches app developers how to build their own apps at LearnAppMaking.com. Since 2009 he has developed a few dozen apps for iOS, worked for global brands and lead development at several startups. When he’s not coding, he enjoys strong espresso and traveling.