Back to blog

How To: Working with Plist in Swift


Aasif Khan
By Aasif Khan | Last Updated on November 17th, 2023 7:08 am | 4-min read

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 app development tutorial 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

Describe your app idea
and AI will build your App

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:



Orange
Pineapple
Raspberry
Banana
Pear
Apple

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 , and 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 tag wraps the array items in it. Every XML element has an opening, with the format, and closing tag, with the format.
  • Inside the array you see 6 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 2 separate 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 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.

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 [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 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 PropertyListEncoder, NSKeyedArchiver, NSCoding and JSONEncoder. They’re worth checking out!

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.


Aasif Khan

Head of SEO at Appy Pie

App Builder

Most Popular Posts