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 tutorial we’ll dive into:
Info.plist
is, and why it’s usefulCodable
Ready? Let’s go.
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 .
If you look closely, you can spot the different values and structures in the XML. Here’s a brief overview from top to bottom:
<?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.<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.<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 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
, 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:
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.
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?
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.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
.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.
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.
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:
PropertyListEncoder
object and setting the output format to .xml
. (Other options are .binary
and the ancient .openStep
.)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.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!
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: