Training and Instructional Design
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.
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]
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>
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.
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.
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.
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
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
// // 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 } }
<?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>
The tutorials here contain a good overview and working code, but sometimes not a lot of developer insights material or explanation of the parameters passed and how choosing one of the other might affect the code:
“MutableContainersAndLeaves”
What would the reader take from that option? What are the alternatives and why would we choose them? What could go wrong by doing that?
Goffredo,
For your question about MutableContainersAndLeaves, I have added a few words about it in the post. I’m surprised that with your knowledge you didn’t see the obvious answer that the
options:
parameter is essentially meaningless, since Swift does not set mutability by class as Objective-C does. It’s only there because of Objective-C. Not to mention what ARC will do to plistData the way the program is written.I do not write exhaustively on a subject. My audience wants an answer they can use immediately. The developer insights I do put in, such as my last paragraph of plist best being read only, or the XML tag chart which I have found no where else (Apple’s is outdated by four years) are ther to be useful to those who have no idea these things exist. When I hit I believe irrelevant to getting the job done, I may skip it as I did with the
options:
parameter.I strongly encourage you to get those “developer insights” out there. Get yourself a free wordpress.com site and start writing. You seem good at curating a few big names or commenting on others, but I think you need to get your own ideas out there. I look forward to reading your insights.
Steve,
I apologise if my original comment appeared rude.
I was not commenting about what I understood or not in your post not commenting about the quality of your content or your books (like the auto layout one I already read which has the same philosophy as you mention in your response), but I wanted to highlight what I Like and think other people might benefit too from this such technical pieces. I do have your work in my Feedly list as I recognise its value, not good at expressing that.
It is what makes the iOS By Tutorials series so useful as reference or the TIJ one valuable. All that goes beyond what the API’s are and their official docs, what comes from the experience of the developers having written that code 10 times already. I am not demanding a lunch for free. It is definitely something I ought to do myself as it is a kind of duty to help others.
No problem. Thank you and I too apologize for not understanding the context. I’ll still urge you to get your own blog/website though. You have a lot of knowledge to share. It would be a shame to waste it. You seem to have a strength in curation more than original writing. Curate others with some original insight of your own. just a thought.
Steve
Pingback: Mobile Development Digest #16 – Alex @ ALSEDI
Pingback: Using Settings Bundles with Swift | Making App Pie
Liked the tutorial.
In the app I’m programming I’m trying to add data in the following format: Int; Name; Definition; [Int]. I tried doing so in an array of arrays like so: “let thing = [[name],[definition],[1,2,3,7]]”. This caused the app to stall out during compile after about 8 entries (I need to make about 100).
Is the property list the appropriate place for this type of data? It is accessed regularly while running the app (but nothing is ever added). I’m trying to avoid doing all the data entry only to discover I’ve put it in the wrong spot.
Love this series of tutorials, I’m hoping they continue through to show when to use core data or other solutions!
Oops – there was actually a whole additional layer, it should have been:
“let thing = [ [[name],[definition],[1,2,3]], [[name2],[definition2],[3,4,5]] ]” etc. No wonder Xcode broke, my head hurts just looking at that.
That’s the point i’d skip the array and use a dictionary, class or Struct instead. Makes my head hurt less.
We will be working our way towards core data, yes.
I wouldn’t put that kind of data in a property list. On the most basic level you could use a .csv text file, which we’ll be discussing late April or Early May, depending if I can get three other things out of the way first (Custom presentation controllers, the two last WatchOS posts and a few podcast recordings). I’ll have better scheduling notes in the newsletter for that on Monday.
Thanks for this tutorial. Do you have any tips on what I can use to capture data selected from this plist for submission outside of the app? Like a basic form submission.
If I understand the question, there’s no good answer except find the requirements of where you are submitting the data to. It’s really up to that server what you do.
You are being asked to login because ashokicreate@gmail.com is used by an account you are not logged into now.
By logging in you’ll post the following comment to How to Use Property Lists (.plist) in Swift:
Hi Steven thanks for tutorial. it’s really helpful.
I am new to ios programming and i have few queries.
As i can see our ultimate goal is to read data from plist as a NSDictionary.
then we can just make use of below code right?
let path = NSBundle.mainBundle().pathForResource(“Config”, ofType: “plist”)
myDict = NSDictionary(contentsOfFile: path)
Which approach is best?
You could do that, it’s more Objective-C than I’d want in my Swift code,but…. I’m not clear the results will always be what you want. You still need to do a lot of error handling, which is what most of that code is. The problems are things beyond your control as a developer: is the XML file well formed and what form is it in? Does the file even exist? Those are things you can’t 100% trust the system, because they are external to your application at times. I’m being very explicit (though this is old swift 2.0 syntax) here to prevent errors which would crash the application. In your suggestion,
path
is an optional. If the file is not found it will crash, becausepath
in thecontensOfFile
parameter is explicit see you’d have to write it like this (Swift 3.0 syntax here):if path is
nil
due to the file not being there or damages or timing out in reading, the app crashes.I prefer to do the error handling while I’m doing the task of reading. Waiting for reading the data and getting
nil
if it isn’t there(which would be the code you suggest) gets messy in my processing code.Pingback: 快速读取属性列表 – CodingBlog
After inserting datasource and delegate lines, a whole lot of errors about “Method does not override any method from its superclass”.
Did I miss something or is this tutorial post – use by date?
Try removing the the overrides. Its a long story but the short version is the delegates and datasource are wrapped up in Swift’s definition of UITableViewController. sometimes you need
override
sometimes you don’t.Thanks – removing the word ‘override’ removed the errors.
This is probably an error that everyone knows about – if you’ve worked on xcode before.
Similarly, I needed to select ‘Is initial View Controller” in the Attributes Inspector for the View Controller in Storyboard; and the following line needed updating, as per suggested ‘fix’:
let cell = tableView.dequeueReusableCell(withIdentifier: “cell”, for: indexPath as IndexPath)
Nope, You are not the only one. Like I said it’s a bit complicated. If you look in the autocomplete at the delegates, you actually see two copies of the same method. one is the delegate and one is the method in UITableViewController which wraps the delegate for ease of use. problem is if you use the delegate directly you don’t override. If you use the method, you do. Xcode is arbitrary (At least I cannot find a pattern) about which one it picks.
And yes this is old code. It is slated for rewrite for 2nd Quarter 2018.
It must be a nightmare to write (or try to follow) tutorials for Swift over the last few years! So many arbitrary changes. I stopped learning Swift for 12-18 months because I couldn’t keep up with it all. After coming back, it doesn’t feel like the changes are slowing down!
Anyway, your explanations are still valid even as the code is depreciated. Here is the code I have, which works, but I am getting nothing out of the data source methods (can’t even print(“HI”)).
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” ]
override func viewDidLoad() {
super.viewDidLoad()
readPropertyList()
}
func readPropertyList()
{
var format = PropertyListSerialization.PropertyListFormat.xml //format of the property list
var plistData: [String:AnyObject] = [:] //our data
let plistPath: String? = Bundle.main.path(forResource: “data”, ofType: “plist”)! // location
let plistXML = FileManager.default.contents(atPath: plistPath!)! // instantiate xml plist
do // use do catch in case plistData doesn’t instantiate
{ // try to serialize xml plist into empty dictionary
plistData = try PropertyListSerialization.propertyList(from: plistXML,
options: .mutableContainersAndLeaves,
format: &format) as! [String:AnyObject]
}
catch
{
print(“Error reading plist: \(error), format: \(format)”)
}
// because dic value types are anytype, they must be converted
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]
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//MARK: – Table View Data sources and Delegates
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
print(“Hello”)
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print(pies.count)
return pies.count
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return “Pies”
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: “cell”, for: indexPath as 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
}
}
I’ll have to look into it. THere’s a lot there which really should be changed anyway. give me a day or so.
Thank you so much for this tutorial, very useful. Do you plan to update it for Swift 4?
Probably late spring or early summer, yes. It is on my list between A GitHub tutorial and and language recognition — assuming no more natural disasters slow me down.
Pingback: How do I get a plist as a Dictionary in Swift? - 天府资讯
Pingback: How do I get a plist as a Dictionary in Swift? - iZZiSwift