This will be the first in a series of lessons on persistence in iOS applications. Persistence is keep data around even if your app ends. As we’ll see in this series there are many techniques to save and read data. One Common one Apple uses frequently in Xcode is the Property list, or plist, named for its extension. In this first lesson we’ll look at how to make, modify and read property lists, and when to use them.
Understanding Property Lists
Open a new project in Xcode with a Single View template using Swift as the language. Save the project as SwiftPropertyListDemo. In the Project inspector, you will find a file info.plist. Select this file.
You will find a screen looking like this:
Many of the settings about your app the compiler needs to know is here. That’s one of the uses for property lists: to keep settings between runs of your application. Right click on the info.plist file name. In the menu that appears select Open as>Source Code
You will get a very different looking screen of XML code starting like this:
<?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"> <dict> <key>CFBundleDevelopmentRegion</key> <string>en</string> <key>CFBundleExecutable</key> <string>$(EXECUTABLE_NAME)</string> <key>CFBundleIdentifier</key> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundleName</key> <string>$(PRODUCT_NAME)</string> <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleShortVersionString</key> <string>1.0</string>
In most cases, a .plist
file is a XML file stored as a dictionary. We can edit property lists either in the property list editor or in the plists. You’ll notice there are key
tags and string
tags and one dict
tag. This makes a dictionary of [String:String]
Understanding Basic XML
If that didn’t make sense to you, Lets discuss some basic XML. XML like HTML is a text file with start and end tags indicated by the characters < and > with an ending indicator of /. Unlike HTML, there are no reserved tags. The author makes up their own. In some program those tags indicate data. You’ll find XML in things like RSS feeds and podcast metadata. As we’ll discuss in a later lesson, some databases can be XML files. Microsoft Word’s .xdoc
file format is actually a XML document.
All XML documents start with a header stating they are a XML document and what kind of document. In the above property list and all property lists we have the following header:
<?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">
In a property list, we have the following tags available
Apple’s internal property list is a dictionary with key:value pairings. You can see here most of the entries are of the form of a key
and a string
, but there are Bool
and array
as well:
<dict> <key>CFBundleDevelopmentRegion</key> <string>en</string> ... <key>LSRequiresIPhoneOS</key> <true/> ... <key>UISupportedInterfaceOrientations</key> <array> <string>UIInterfaceOrientationPortrait</string> <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> </dict>
Making Your Own Property Lists
You can make your own property lists. Within Xcode, there is a file template. Press Command-N to get a new file. Under iOS Resource select Property list
Name the property list data, and store it in the SwiftPropertyListDemo group folder. You will see a blank property list in a dictionary.
Hover over the root entry and you will find a + button to add an entry
Click the button and a new entry appears, with a default type of String
Click the type selector indicating String, and you will find the following selections
Select Number. Change the value to 0.3, and the key to Red
Press root’s add button two more times and add a Green value of 0 and Blue as a blank string.
We’ll use these three values to save a color. While there is a better way to save colors that will be covered in another lesson, this is a simpler way for us to set a color from a property list.
Click the + button on Root. Make an entry with the key Pies and a type array. You’ll get an arrow next to the key Pie . Press the + button on the Pies key. You’ll get another entry.
The + on any entry makes another entry below it. Since we do not want this entry, click the – on the key to delete it. Notice the triangle next to Pies click it so it points down. In order to get elements in a dictionary or array you must click this to point down. Click the + on the pie entry four times to get four elements to your array.
Keeping the array elements as strings add four pies to the property list.
Click the Root + button. Add a boolean entry with the key Inverse Colors set to NO.
You can re-arrange your items by click and dragging up and down. You’ll see a blue line indicating where the item will be inserted. Re-arrange the keys like this. As far as the compiler is concerned you don’t have to do this, but it is easier for humans to read and understand.
Editing the XML Property List
Let’s look at the XML for our property list. Right Click data.plist and select Open As>Source Code. We get the following code
<?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"> <dict> <key>Inverse Color</key> <false/> <key>Red</key> <real>0.3</real> <key>Green</key> <key>Green</key> <integer>0</integer> <key>Blue</key> <string></string> <key>Pies</key> <array> <string>Cherry</string> <string>Apple</string> <string>French Silk</string> <string>Coconut Cream</string> </array> </dict> </plist>
Green is an integer and Blue is a string. The editor assumes an integer if you do not include a decimal point. Let’s change both real
and add a value of 0.3
to the Blue entry. Change this
<key>Green</key> <integer>0</integer> <key>Blue</key> <string></string>
to this
<key>Green</key> <real>0.0</real> <key>Blue</key> <real>0.3</real>
Let’s add two more pies to our array, under the entry for Coconut Cream add the following:
<string>Blueberry</string> <string>Boston Cream</string>
Switch back to the editor by selecting Open As> Property list and you will see your changes to the property list.
Setting up a Demo Application
Let’s set up a simple table view application to use our new property list. If you are not familiar wth table view controllers I’d suggest reading this first. Go to the story board and delete the current view controller. Drag a table view controller to the storyboard. In the attribute inspector, click on Is Initial View Controller. Select the table view cell and change the Style to Basic and the Identifier to cell.
Now go to the View Controller. Change the view controller to subclass UITableViewController instead of View Controller
class ViewController: UITableViewController {
Go back to the storyboard and select the table view controller. In the Identity inspector, set the Class to View Controller.
Go to your view controller code. Add the following properties with some test data:
var isColorInverted = true var color1 = UIColor( red: 0.3, green: 0.3, blue: 0.0, alpha: 1.0) let color2 = UIColor( white: 0.9, alpha: 1.0) var pies = [ "Pizza", "Chicken Pot", "Shepherd's" ]
Add the delegates and data sources. At the bottom of the class add this:
//MARK: - Table View Data sources and Delegates override func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1 } override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return pies.count } override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return "Pies" } override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) let row = indexPath.row cell.textLabel?.text = pies[row] if isColorInverted { //invert colors by flag status cell.backgroundColor = color2 cell.textLabel?.textColor = color1 }else{ cell.backgroundColor = color1 cell.textLabel?.textColor = color2 } return cell }
We’ll display the value of the array in the cells and change the color of the table view cell depending on the value of isColorInverted
. Now build and run this to make sure the app works with the test data.
Reading a .plist in Swift
Add the following above the viewDidLoad
method.
func readPropertyList(){ }
We’re going to make this a function that we’ll call in our viewDidLoad
to set the properties for the class according to the property list. Add the following property to the new function:
var format = NSPropertyListFormat.XMLFormat_v1_0 //format of the property list
The variable format
is the format of the property list. While XML is the most common there are a few other types available all listed in the enumeration NSPropertyListFormat
. This will be used in a parameter later on, but needs an initial value. The method we’ll run later may change format to a different format during the conversion process so it will be a pass through variable. Add under this property
var plistData:[String:AnyObject] = [:] //our data
We’ll use plistData
to store the dictionary entries of the property list. I’m making an assumption here you may not want to make. For almost all cases a property list will be a dictionary, so I’m setting this up for a dictionary. That might be a bad assumption and you can alternately write var plistData:AnyObject! = nil
. or whatever format you are expecting from the property list. Now add this line
let plistPath:String? = NSBundle.mainBundle().pathForResource("data", ofType: "plist")!
This finds and returns the path of our list from the main bundle. I’m assuming it is in the main bundle here or there is only one data.plist in the bundle. Both could cause errors. For simplicity I made sure it was. You should add more robust code to find it where it may be hiding or if there are duplicates. Now add this
let plistXML = NSFileManager.defaultManager().contentsAtPath(plistPath!)!
The constant plistXML
is the XML data as type NSData
format. The function may return nil
if the file is not found. Now add this under it
do{ plistData = try NSPropertyListSerialization.propertyListWithData(plistXML, options: .MutableContainersAndLeaves, format: &format) as! [String:AnyObject] } catch{ print("Error reading plist: \(error), format: \(format)") }
Here we convert the XML data to a dictionary. There is a class, NSPropertyListSerialization
which along with the class method propertyListWithData
does the conversions. It contains several parameters to discuss. The first parameter is the data we will convert with is of Type NSData
, which we will discuss in more detail in the next lesson in this series. The second parameter is a constant from the enum NSPropertyListMutabilityOptions
. This parameter has three possible options which affect the mutability of the dictionary created. It’s a feature for Objective-C, not Swift. In Swift it’s meaningless. The parameter format
is a pass through variable, so needs the &format
. If we did not use it in the print statement, we could also put
While I’ve been fast and loose with error checking for this demo, this is the one place I’m careful. If you edit your plist in XML on a text editor, you have the chance of introducing syntax errors which will mess up everything. For that reason, I use Swift’s error control do...try...catch
clause to catch syntax errors in the XML.
If this step is successful, we have in plistData
a dictionary of our property list. Add the following code to assign the Bool
value in the do
block
isColorInverted = plistData["Inverse Color"] as! Bool
The value of dictionary is of type AnyObject
. For each value we need to downcast it to assign it properly. For isColorInverted
, that is a Bool
. For the XML tags real
and integer
, it can be any real or integer type. Add this code :
let red = plistData["Red"] as! CGFloat let green = plistData["Green"] as! CGFloat let blue = plistData["Blue"] as! CGFloat color1 = UIColor(red: red, green: green, blue: blue, alpha: 1.0)
We did not have to downcast to Float
and then convert to a CGFloat
. We can Downcast directly to CGFloat
. Add this to assign the array pie
pies = plistData["Pies"] as! [String]
The function is now complete and will assign to the properties of the class values from the the property list. Change viewDidLoad
to use it.
override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. readPropertyList() }
Build and run. You will get different results than your first run
When to Use plists
I’m conservative with using plists. While you can write plists, I never do. I’m of the thought that property lists should be a read-only at run time. While there are several reasons for that, my biggest is there are much better ways to save data than plists. I use them for two things only. Instead of setting global constants, I’ll make a plist and load it into the classes that need it. A small data file is a lot easier to change than finding a constant in a mass of code. Secondly, I use it as I did here to load tables and pickers with static data. Instead of making a huge amount of code to set arrays, I make it an external file. These both are read-only operations. In the lessons the follow, we’ll look at some of the ways to both read and write data.
If you liked this lesson and would like to be notified about more lessons, subscribe to the Make App Pie NewsletterWhich sends a new iOS lesson every Thursday and news and sneak previews of upcoming works every Monday
The Whole Code
ViewController.swift
// // ViewController.swift // SwiftPropertyListDemo // // Created by Steven Lipton on 2/10/16. // Copyright © 2016 MakeAppPie.Com. All rights reserved. // import UIKit class ViewController: UITableViewController { var isColorInverted = true var color1 = UIColor(red: 0.3, green: 0.3, blue: 0.0, alpha: 1.0) let color2 = UIColor(white: 0.9, alpha: 1.0) var pies = ["Pizza","Chicken Pot","Shepherd's"] func readPropertyList(){ var format = NSPropertyListFormat.XMLFormat_v1_0 //format of the property list var plistData:[String:AnyObject] = [:] //our data let plistPath:String? = NSBundle.mainBundle().pathForResource("data", ofType: "plist")! //the path of the data let plistXML = NSFileManager.defaultManager().contentsAtPath(plistPath!)! //the data in XML format do{ //convert the data to a dictionary and handle errors. plistData = try NSPropertyListSerialization.propertyListWithData(plistXML,options: .MutableContainersAndLeaves,format: &format)as! [String:AnyObject] //assign the values in the dictionary to the properties isColorInverted = plistData["Inverse Color"] as! Bool let red = plistData["Red"] as! CGFloat let green = plistData["Green"] as! CGFloat let blue = plistData["Blue"] as! CGFloat color1 = UIColor(red: red, green: green, blue: blue, alpha: 1.0) pies = plistData["Pies"] as! [String] } catch{ // error condition print("Error reading plist: \(error), format: \(format)") } } override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. readPropertyList() } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } //MARK: - Table View Data sources and Delegates override func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1 } override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return pies.count } override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return "Pies" } override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) let row = indexPath.row cell.textLabel?.text = pies[row] if isColorInverted { //invert colors by flag status cell.backgroundColor = color2 cell.textLabel?.textColor = color1 }else{ cell.backgroundColor = color1 cell.textLabel?.textColor = color2 } return cell } }
data.plist
<?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"> <dict> <key>Inverse Color</key> <false/> <key>Red</key> <real>0.3</real> <key>Green</key> <real>0</real> <key>Blue</key> <real>0.3</real> <key>Pies</key> <array> <string>Cherry</string> <string>Apple</string> <string>French Silk</string> <string>Coconut Cream</string> <string>Blueberry</string> <string>Boston Cream</string> </array> </dict> </plist>
Leave a Reply