Tag Archives: property list

Using Settings Bundles with Swift

Have you ever wondered how to put  user defined settings for your app into the settings app?  Xcode can create a special property list called a settings bundle which can append the  NSUserDefaults with more entries from Settings App.  You’ll find out in this lesson how to set up a settings bundle in your app for using the Settings App using XML. In the next lesson we’ll dive deep into the settings bundle using the property list editor.

I’ll assume you know how to use property lists and NSUserDefaults in this lesson. If you are not familiar with them you might want to check out my recent posts on Property Lists and NSUserDefaults

Create the  Settings Bundle

Start a new Single view application  project in Xcode called SettingsBundleDemo using Swift and a Universal device.

Right-click on the SettingsBundleDemo group folder and select New file. Select the  Resources  category in the template window. Select Settings Bundle and click Next.

2016-03-11_05-57-54

You should have name of settings. Keep the setting bundle in the SettingsBundleDemo Group  in the save window. Click Create.

The system will open up the settings bundle. which looks like a building block. In the Navigator open up the settings bundle by clicking on the arrow.

2016-03-11_06-00-28

Click on the Root.plist file. You’ll see a property list  like this:

2016-03-13_12-33-22

If closed, Open the Preference Items array. This is populated with demo data. Unlike other property lists,  this one does not directly store the value as a dictionary of values, but an array of dictionaries of controls. You pick the control you want in the Settings app by listing it in the property list. When we attach the settings bundle to the standard defaults of NSUserDefaults, the system will create key values in the NSUserDefaults.

The following table lists the type of controls that you can use:

2016-03-13_15-54-53

Each type of control has several attributes.   If not already open, Open the Group Control, the Item 0 entry,  in Root.plist.

2016-03-13_12-38-07

Groups are control used to group other controls together. They have only a title and a Type. Open the Item 3 (Slider).

2016-03-13_12-39-56

The slider has more required controls, the ones listed here.  Many controls will have a  poorly named attribute Default Value. This is the value shown in  the settings app, not a true value of the default. It will show the values after the first use, but it is for display purposes not value purposes.

Each control type has its own attributes, as described in the chart above.  Here’s a list of all the possible attributes for the property list.

2016-03-14_08-11-55

Making Your Own Settings

Delete the current entries. Property lists do not delete well in Root.plist.  The simplest way is to cut them out. Select Item 0 (Group). Right click on group, and select Cut in the menu. Then do the same for the Item 3( Slider)  and the rest of the controls. You should have a clean property list like this.

2016-03-13_12-43-40

Select Preference Items.  Make sure it is open. The arrow should be pointing down.  Press the Add (+) button on the entry. A new entry appears.

2016-03-13_12-48-19

There will also be a menu that opens. If you happen to click somewhere and lose the menu, click the down chevron(2016-03-13_12-50-18)  in the entry. You get a list of controls.

2016-03-13_12-51-21

Select Toggle Switch. This will give us a Bool value.  Open up the toggle switch control and you have the following attributes:

2016-03-13_12-53-55

Click the value for each blank attribute, and set Title to  Room for cream.  Set Identifier to coffee_cream.  The identifier is the key for the NSUserDefaults entry.  Set the Default Value to YES. Your attributes should look like this:

2016-03-13_12-57-07

Close the attributes for the switch, and select Item 0. Right click and select Add Row from the menu. Although it is the default, in the type menu, select Text Field.   Open up the Item 1(Text Field – ) to see the attributes.

2016-03-13_13-01-53

 

Set the Title to Beverage of choice.  Set the Identifier to coffee_type. Right click on Identifier and select Add Row. A new row appears asking for an appropriate attribute:

2016-03-13_13-08-55

Select the default Default Value. Set the Default Value to Coffee.

2016-03-13_13-08-56

Close the attributes for the Text menu. Select the  Preference items entry, right click  and select Add Row.  The new row pushed the other rows down becoming Item 0. Select Multi-Value. Multi-value uses two arrays to make a selection table. Open up the attributes of the multi-value.  Set the title to Size. Set the Default Value to 0. Set the Identifier to coffee_size.

2016-03-13_13-28-32

This control does not by default set up values. You must do that. Select the bottom attribute,  Right-click the entry, then choose Add Row.  Make the row type Values. Open Values‘ array, which should be empty. Using either the Add row or the + Icon, add four  sub rows to Values. Add the  number  0  to the first  row. Change the type of the row to Number.  Do the same for the other rows, making the values 1,  2 and 3.

2016-03-13_13-28-33

Close the Values array. Add another attribute to the multi-value control Titles. Open the attribute’s array and add four entries. These will be  the strings Small, Medium, Large, and Extra Large.

2016-03-13_13-31-04

We can look at our settings page now.  Just to make it easy to know when we can look at it, set the back ground in the Launchscreen.storyboard to Orange(#ff8000), and the Main.Storyboard to Yellow(#FFFF00). Build and run. When the Background turns yellow, go to the settings app.  If on a live device, hit the Home button. If on the simulator,  hit Command-Shift-H on your keyboard for the home button. Navigate to the settings app.

2016-03-13_13-42-05

Scroll down to the bottom of the app. You’ll find our app there.

2016-03-13_13-42-25

Click the item and we get a settings page.

2016-03-13_13-42-58

Setting up the Storyboard

Stop the app, then go to  Main.storyboard in Xcode. Add a switch, a label, a text field  and  a segmented control to the storyboard. Configure the controls to look like this:

2016-03-13_14-27-04

Select the switch. In the size inspector, set Compression Resistance and Content Hugging to 1000 horizontally and vertically. Switches should never change size, and this prevents that from happening.

2016-03-13_14-36-49

Click the stack view button2016-03-13_14-36-07 to make a horizontal stack view. Select everything on the storyboard, and make vertical stack view by pressing the stack view button2016-03-13_14-36-07 again.  Set the Stack View’s attributes to this:

2016-03-13_14-43-19

Pin the stack view 71 up, 5 left and 5 right. Update frames for items of new constraints.

2016-03-13_14-50-17

Open up the assistant editor and connect up the following outlets to their proper controls:

@IBOutlet weak var roomForCream: UISwitch!
@IBOutlet weak var drinkText: UITextField!
@IBOutlet weak var sizeSegment: UISegmentedControl!

Reading the Settings Bundle

The NSUserDefaults does not know we have a settings bundle. Our first task is to register the settings bundle with NSUserDefaults.

func registerSettingsBundle(){
   let appDefaults = [String:AnyObject]()
   NSUserDefaults.standardUserDefaults().registerDefaults(appDefaults)
}

This registers the settings bundle to the NSUserDefaults standard defaults. the registerDefaults method looks for property lists in the resources directory and changes the value:key entries to dictionaries of the form [String:AnyObject]. We only run this once in our code.

Make a new function updateDisplayFromDefaults and get our defaults

func updateDisplayFromDefaults(){
//Get the defaults
    let defaults = NSUserDefaults.standardUserDefaults()
}

Read the key:value pairs and assign them to our outlets. Add this to the function:

//Set the controls to the default values. 
    roomForCream.on = defaults.boolForKey("coffee_cream")
    if let drink = defaults.stringForKey("coffee_type"){
        drinkText.text = drink
    } else{
        drinkText.text = ""
    }
    sizeSegment.selectedSegmentIndex = defaults.integerForKey("coffee_size")

The stringForKey method returns a optional value. Our code checks for a nil value and reacts accordingly. Add the new function to viewDidload:

override func viewDidLoad() {
    super.viewDidLoad()
    registerSettingsBundle()
    updateDisplayFromDefaults()
 }

We want to start fresh when we load the app. Go to the simulator or your device. Delete the application by pressing and holding on the app until it shakes. Once shaking, click the X to delete it. You will be asked

2016-03-13_15-03-16

Click Delete and the app and the settings are now deleted. Build and run the application. We start with a blank preferences, since they haven’t been written to the app yet. Since the Default Value of the property list is a display value, it does not reflect here.

2016-03-13_15-14-09

Press Command-Shift-H, then navigate over to the settings. Set your settings to this:

2016-03-13_15-20-13

Press Command-Shift-HH to and select the demo app. Our app hasn’t changed.

2016-03-13_15-14-09

Close the app in Xcode. Re-run the app. Now we get our defaults.

2016-03-14_07-56-20

Updating Defaults with Observers

Updating our preferences on restart only  is not a good thing. The problem is we need to tell the app that there was a change. This needs to be done automatically.In both a settings bundle and for NSUserDefaults within an app, this will be a frequent problem. You change a setting and want everywhere that uses it to update. Such changes need notification observers. I think of fire watchers as an analogy to observers. You have a forest ranger sitting in a tower looking for fire in the forest below. If she finds one, then she sets off an alrm to fight the fire to everyone in the forest. An observer does the the same thing in code. It looks for a certain event to happen, then runs a target method if is sees it happening. These events can be system defined events known as notifications (not to be confused with push notifications that show up on your device) or you can make up your own. I’ll discuss how to make notifications in a future post. We’ll concentrate on system generated ones.

The NSUserDefaults class generates a notification NSUserDefaultsDidChangeNotification. We can set an observer for the notification, and then run some code to update the display. Change viewDidLoad to this:

override func viewDidLoad() {
    super.viewDidLoad()
    registerSettingsBundle()
    updateDisplayFromDefaults()
        
    NSNotificationCenter.defaultCenter().addObserver(self,
        selector: "defaultsChanged",
        name: NSUserDefaultsDidChangeNotification,
        object: nil)
    }

We call the default notification center which keeps track of our observers to register it. This center is NSNotificationCenter.defaultCenter(). We add the observer with name NSUserDefaultsDidChangeNotification and tell it to run the function defaultsChanged when it observes a notification.

As an aside, you might be tempted to add the code from registerSettingsBundle as part of your updateDisplayFromDefaults. Every execution of registerSettingsBundle changes the NSUserDefaults by adding more defaults. Each time we add a default we get a notification, causing a recursive death spiral until we run out of memory. Keep it separate.

We’ll need a small function defaultsChanged to run when we have a notification. Add this to your code:

func defaultsChanged(){
        updateDisplayFromDefaults()
    }

There’s also a bit of legacy code we have to add. Prior to iOS9, we need to remove the observer for good memory management. In iOS9 and later, ARC does that for us. For any iOS8 devices, add the following to remove the observer.

deinit { //Not needed for iOS9 and above. ARC deals with the observer.
NSNotificationCenter.defaultCenter().removeObserver(self)
}

The code uses the Swift class’ deinint function to remove the observer from NSNotificationCenter.defaultCenter().

A Bug in the Simulator

Build and run. Hit Command-Shift-H then switch over to the settings app. Select the settings for the SettingsBundleDemo and… It’s blank.

2016-03-13_15-19-17

This doesn’t happen on a device. Only the simulator. There is a workaround. It seems the simulator is still running your last iteration of your app. When you hit stop in Xcode it doesn’t stop the settings app. Click Command-Shift-HH to double-click the home button. Swipe up on the Settings app to kill the process. Start the Settings again, and settings refreshes itself. You’ll have a settings page again. Change the settings to this.

2016-03-14_07-33-44

Go back to the Settings Bundle Demo. Our results show up this time.

2016-03-14_07-34-16

That’s the basics of Settings Bundles. There are a few more advanced options such as child settings pages and coding the settings directly in XML. The charts above have the tags for XML and there is is the Root.plist code below in XML for your further exploration.

The Whole Code

//
//  ViewController.swift
//  SetttingsBundleDemo
//
//  Created by Steven Lipton on 3/11/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var roomForCream: UISwitch!
    @IBOutlet weak var drinkText: UITextField!
    @IBOutlet weak var sizeSegment: UISegmentedControl!
    
    deinit { //Not needed for iOS9 and above. ARC deals with the observer.
        NSNotificationCenter.defaultCenter().removeObserver(self)
    }
    
    func registerSettingsBundle(){
        let appDefaults = [String:AnyObject]()
        NSUserDefaults.standardUserDefaults().registerDefaults(appDefaults)
        //NSUserDefaults.standardUserDefaults().synchronize()
    }
    
    func updateDisplayFromDefaults(){
    
        
     
        //Get the defaults
        let defaults = NSUserDefaults.standardUserDefaults()

        //Set the controls to the default values. 
        roomForCream.on = defaults.boolForKey("coffee_cream")
        if let drink = defaults.stringForKey("coffee_type"){
            drinkText.text = drink
        } else{
            drinkText.text = ""
        }
        sizeSegment.selectedSegmentIndex = defaults.integerForKey("coffee_size")
    }

    func defaultsChanged(){
        updateDisplayFromDefaults()
    }
    @IBAction func updateDefaults(sender: AnyObject) {
        updateDisplayFromDefaults()
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        registerSettingsBundle()
        updateDisplayFromDefaults()
        NSNotificationCenter.defaultCenter().addObserver(self,
            selector: "defaultsChanged",
            name: NSUserDefaultsDidChangeNotification,
            object: nil)
        
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


}


Root.plist

While we did not discuss them here, you can make entries in XML to your property list. Here’s the property list for the demo in 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">
<dict>
	<key>StringsTable</key>
	<string>Root</string>
	<key>PreferenceSpecifiers</key>
	<array>
		<dict>
			<key>Type</key>
			<string>PSMultiValueSpecifier</string>
			<key>Title</key>
			<string>Size</string>
			<key>Key</key>
			<string>coffee_size</string>
			<key>DefaultValue</key>
			<string>0</string>
			<key>Values</key>
			<array>
				<integer>0</integer>
				<integer>1</integer>
				<integer>2</integer>
				<integer>3</integer>
			</array>
			<key>Titles</key>
			<array>
				<string>Small</string>
				<string>Medium</string>
				<string>Large</string>
				<string>Extra Large</string>
			</array>
		</dict>
		<dict>
			<key>Type</key>
			<string>PSToggleSwitchSpecifier</string>
			<key>Title</key>
			<string>Room for cream</string>
			<key>Key</key>
			<string>coffee_cream</string>
			<key>DefaultValue</key>
			<true/>
		</dict>
		<dict>
			<key>Type</key>
			<string>PSTextFieldSpecifier</string>
			<key>Title</key>
			<string>Beverage of Choice</string>
			<key>Key</key>
			<string>coffee_type</string>
			<key>DefaultValue</key>
			<string>Coffee</string>
		</dict>
	</array>
</dict>
</plist>

How to Use Property Lists (.plist) in Swift

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.

2016-02-10_05-52-25

You will find a screen looking like this:

2016-02-10_05-53-23

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

2016-02-10_06-01-03

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

property list XML tags

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

2016-02-10_07-06-42

Name the property list data, and store it in the SwiftPropertyListDemo group folder. You will see a blank property list in a dictionary.

2016-02-10_07-10-41

Hover over the root entry and you will find a + button to add an entry

2016-02-10_07-13-00

Click the button and a new entry appears, with a default type of String

2016-02-10_07-15-25

Click the type selector indicating String, and you will find the following selections

2016-02-10_07-15-26

Select Number. Change the value to 0.3, and the key to Red

2016-02-10_07-15-27

Press root’s add button two more times and add a Green value of 0 and Blue as a blank string.

2016-02-11_06-55-24

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.

2016-02-11_07-06-39

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.

2016-02-11_07-11-58

Keeping the array elements as strings add four pies to the property list.

2016-02-11_07-14-08

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.

2016-02-11_07-20-32

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.

2016-02-11_07-55-22

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.

2016-02-11_07-26-20

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.

2016-02-11_07-29-52

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

2016-02-11_07-42-07

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>