Tag Archives: NSUserDefaults

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 NSUserDefaults in Swift.

While Property Lists can be ways of storing data, there is a better way of handling small amounts of data for preferences and settings that might change over time and user. While built on a property list, NSUSerDefaults Makes for a more convenient way for such data. IN this lesson, we’ll introduce NSUserDefaults and a few more classes which will be helpful is storing several types of data we weren’t transferring with a basic property list.

Make a New Project

We’ll write an app that changes the background color using sliders. Our app will save our settings for color, a selected segment and the date we saved the configuration. Open Xcode and make a new project named SwiftUserDefaultsDemo, using a single view template and Swift as the language. Open the storyboard and roughly set it up like this:

2016-02-24_05-57-11

The background is Light Gray(#AAAAAA). For the segmented control I added two more segments, and I set the tint to Black(#000000). I added the three sliders and used a Red(#FF0000), Green(#00FF00), and Blue(#0000FF) thumb to indicate their function.
Just to keep this clean, I’ll use stack views. You can skip this if you want a messier app. If you are not familiar with the beauty that is stack views, you can check out this tutorial. Start by selecting the Dark Text Label and the switch. Click the stack view button at the bottom of the storyboard.

stackButton

We will use the default values of horizontal stack view, with Fill for Alignment and Distribution. Select the switch. Go to the size inspector and change the Content Huggingfor both Horizontal and Vertical to Required(1000)

2016-02-24_06-10-27

Marquee-select all the controls on the storyboard. Click the stack view button again. In the attributes inspector, set the stack view attributes to this:

2016-02-24_06-19-01

Pin the stack view using the auto layout pin button pinMenuButton 10 top, 10 left and 10 right. Select the Update Frames with Items of new constraints at the bottom of the popup then click Add 3 constraints.

Finally add a button on the bottom of the storyboard. Make the background of the button a Dark Gray(#555555) and the text White(#FFFFFF). Set the text to Save. Using auto layout, Pin the button 10 left, 10 right and 20 down. Set the Height to 60 points. Select the Update Frames with Items of new constraints at the bottom of the popup then click Add 4 Constraints. Your final layout should look like this:

2016-02-24_06-56-40

Set Up Outlets and Actions

Go to the ViewController.swift file. Add the following properties, outlets and actions:

//MARK: Outlets and properties
    
    var dateUsed = NSDate()
    @IBOutlet weak var dateLabel: UILabel!
    @IBOutlet weak var segmentedControl: UISegmentedControl!
    @IBOutlet weak var redSlider: UISlider!
    @IBOutlet weak var greenSlider: UISlider!
    @IBOutlet weak var blueSlider: UISlider!
    @IBOutlet weak var textColorSwitch: UISwitch!
    @IBOutlet weak var rgbLabel: UILabel!
    @IBOutlet weak var darkTextLabel: UILabel!
    
    //MARK: - Actions
    @IBAction func textColorSwitch(sender: UISwitch) {
    }
    
    @IBAction func colorSliderChange(sender: UISlider) {
    }
    
    @IBAction func saveButton(sender: UIButton) {
    }

Go to the storyboard and open up the assistant editor. You can close the navigator and the attributes inspector to give yourself more room. From the circle next to each outlet, drag to the appropriate control on the storyboard. Do the same for the textColorSwitch and the saveButton actions. For the colorSliderChange action, drag three times, once to each of the three sliders. We’ll control all three sliders with the same code.

Code the Demo App

Close the assistant editor. Open the navigator and head over to the ViewController.swift file. Code the textColorSwitch and the colorSliderChange like this:

@IBAction func textColorSwitch(sender: UISwitch) {
        updateColor(background: true)
    }
    
    @IBAction func colorSliderChange(sender: UISlider) {
        updateColor(background: true)
    }

Next we’ll code this new function updateColor. Add the following under the actions:

//MARK: - Instance Methods
func updateColor( background background:Bool){
    //update background color
    if background{
    view.backgroundColor = UIColor(
        red: CGFloat(redSlider.value),
        green: CGFloat(greenSlider.value),
        blue: CGFloat(blueSlider.value),
        alpha: 1.0)
    }
    //update the text color
    var textColor = UIColor()
    if textColorSwitch.on{
        textColor = UIColor.blackColor()
    } else {
        textColor = UIColor.whiteColor()
    }
    dateLabel.textColor = textColor
    darkTextLabel.textColor = textColor
    rgbLabel.textColor = textColor
    segmentedControl.tintColor = textColor
}

This is just a quick way of updating our background and text colors depending on the values for our controls. There’s a Bool parameter background for something we’ll test later. We now have enough code to check our switch and sliders. Build and run. You should get this:

2016-02-24_06-55-18

Move the sliders around, and the background color changes.

2016-02-24_06-55-41

Slide the sliders to the left to make a dark background. Change the text switch.

2016-02-24_06-56-05

Our sliders and switch work. We also should update the RGB label and date label. At the bottom of the updateColor action, add this:

//update label for RGB color
        rgbLabel.text = String(
            format: "RGB: %1.2f, %1.2f %1.2f",
            redSlider.value,
            greenSlider.value,
            blueSlider.value)

In viewDidLoad, add this:

override func viewDidLoad() {
        super.viewDidLoad()
        dateLabel.text = formattedDate(dateUsed)
    }

This adds another function, a date formatter to our code. Add this to your code:

func formattedDate(date:NSDate) -> String{
        let formatter = NSDateFormatter()
        formatter.timeStyle = .ShortStyle
        formatter.dateStyle = .MediumStyle
        return formatter.stringFromDate(date)
    }

This gives us a medium style date and a short style time with a default locale. If you are not familiar with date formatters, you might want to check out my post on them. We’re ready to build and run again. Move the sliders a bit and we get this:

2016-02-24_07-19-17

Saving Preferences with NSUserDefaults

Up to this point our app starts with the gray background. We’ll use NSUserDefaults to save our preferences for a background and other settings. NSUserDefaults is essentially a wrapper around a property list that has easier to use methods than a straight property list. We make a property list object, then use methods to save or retrieve the type of data we have.
Add this to our properties for the ViewController class:

let defaults = NSUserDefaults.standardUserDefaults()

This uses the class method standardUserDefaults. This does one of two things: Creates the standard defaults file and configures it if it does not exist, or opens it if it does. For most cases you’ll use this to make your defaults.
To add a default setting to the NSUserDefaults, we use a method for the type we are setting. In the saveButton action, add the following:

 @IBAction func saveButton(sender: UIButton) {
        defaults.setBool(textColorSwitch.on, forKey: "DarkText")
}

This adds the textColorSwitch‘s Bool value on to a key named DarkText. Note for demonstration purposes I’m using a string literal for the key. It’s a good practice to use string constants instead to prevent several bugs due to misspellings of keys.
In earlier versions of iOS, you would then use the synchronize method to save this. In most cases where you are coding in Swift, this is unnecessary. According to Apple, synchronize is ultimately counter-productive, which is why we won’t use it here. synchronize is now a scheduled event, and will happen eventually. You’ll therefore see the notable exception: If you are exiting an app and wanting to save your preferences before closing, you would need a synchronize.

We can also add entries for Int and Float. Change saveButton
to this:

@IBAction func saveButton(sender: UIButton) {
    defaults.setBool(textColorSwitch.on, forKey: "DarkText")
    defaults.setInteger(segmentedControl.selectedSegmentIndex, forKey: "SegmentIndex")
    defaults.setFloat(redSlider.value, forKey: "Red")
    defaults.setFloat(greenSlider.value, forKey: "Green")
    defaults.setFloat(blueSlider.value, forKey: "Blue")

We’ve added the positions of the sliders and the segment index of our segmented control. Except for the name of the method, there’s little difference in their use from setBool. This is why NSUserDefaults is so much better for defaults than setting up your own property list. Much of the hard work is done for you. There are several methods for setting and getting data into NSUserDefaults. Here’s a table of them:

NSUserDefaults Getters and setters

The setObject method and NSDate

Most of the simple data types we already talked about such as Bool and Float are on the chart above. A few types such as String and NSDate don’t have their own setter, but use setObject. These types are that same types we encountered in property lists. I’ll refer to them as property list objects. The property list objects NSData, NSDate, NSNumber, String, arrays, and dictionaries all go into user defaults using setObject.

In our app, we will use a date example. Add another line to our setters:

defaults.setObject(NSDate(), forKey: "Date")

While the current app grabs the date and time we started the app, this will remember the last time we saved the app.

Saving Colors and Other Objects with NSData.

We’ve already stored enough information to make a color with the float values we stored. This may get cumbersome with more than one color. It makes more sense to store the color directly. Like UIColor, we might want to store objects in our user preferences that are not property list objects. The trick here is the class NSData. You can convert most objects to NSData. We’ll convert the background color to NSData and then store it in the preferences. Add this to our saveButton action:

//Storing the color and any other non-data object
let color = view.backgroundColor!
let colorData = NSKeyedArchiver.archivedDataWithRootObject(color)
defaults.setObject(colorData, forKey: "Color")

The first line gets our color form the view’s background. The second line uses a class method of NSKeyedArchiver to archive the object. What’s going on under the hood is beyond the scope of this tutorial, but essentially it makes an easily readable binary object. This is an NSData object, which we can use setObject to make it a user preference.

A word of caution about images. You can encode images as NSData, but don’t do it. They take up a lot of space and take a long time to encode and decode. A better approach for images is to store the image with a URL and then store the image’s URL in the user preferences with the setURL:forKey: method.

This is all we need to save our settings for this app.

Getting the User Defaults

Of course saving preferences is not enough, we have to read them back as well. Make a new function readDefaults:

func readDefaults(){
}

Let’s set the switch for text color first. Add the following code:

textColorSwitch.on = defaults.boolForKey("DarkText")

We’re reading a property list, and the property list is set up as a dictionary. We use a key DarkText to get a Bool value. All these getters are immutable. It makes little sense to keep them around as variables. We assign that value directly to the switch’s on property. We can do the same for the segmented control. Add this line:

segmentedControl.selectedSegmentIndex = defaults.integerForKey("SegmentIndex")

This does the same as the previous line using integerForKey for the SegmentIndex key to get our selected segment index. We can set the sliders using the floatForkey method. Add this:

redSlider.value = defaults.floatForKey("Red")
greenSlider.value = defaults.floatForKey("Green")
blueSlider.value = defaults.floatForKey("Blue")

If you look at the chart above, you’ll notice a note about what happens for these methods when the key is not found. It returns a specific value of false for the Bool and 0 for the numbers. While in our app that is not tragic, it could be a problem in other apps. You will need some more code or some debugging to determine if it a value of 0 or false or it is a missing key. Later on we’ll see what effect this has on our app.

That is not a problem with objects using setObject. Its counterparts including objectForKey return an optional value, where nil is a not found object. Some people don’t use the simpler values mentioned about and use setObject for numbers and Bool values just to have the optional value. To use setObject is slightly different to detect the nil. We’ve got a NSDate object in our prefrences as an example. We read it like this:

if let dateObject = defaults.objectForKey("Date") {
    let date = dateObject as! NSDate
    dateLabel.text = "Previous save: " + formattedDate(date)
}

We optionally chain the defaults.objectForKey("Date") to check if it exists. If it does, the code downcasts the returned result to a date. Once we have a date we change the date label. If not, we leave the default setting of the label we set in viewDidLoad.

For our color object, things get a bit more complicated since there is no default like the date. We’ll need an else to control for that situation. Add this to our code:

if let colorObject = defaults.objectForKey("Color") {
     let colorData = colorObject as! NSData
     let color = NSKeyedUnarchiver.unarchiveObjectWithData(colorData) as? UIColor
     view.backgroundColor = color
     updateColor(background: false)
} else{
    updateColor(background: true)
}

I broke the steps out here. Like the NSDate, we optionally chain to find the key. Once found, we downcast the value of colorObject to NSData. That’s the same as the date getter in structure. The next step is different though. The code converts the NSData object back to a color. With that color, we update the background color. Since we don’t need the slider values to update the color for us, we call updateColor to not update the background with a false parameter.

Our finished function looks like this

func readDefaults(){
    textColorSwitch.on = defaults.boolForKey("DarkText")
    segmentedControl.selectedSegmentIndex = defaults.integerForKey("SegmentIndex")
    redSlider.value = defaults.floatForKey("Red")
    greenSlider.value = defaults.floatForKey("Green")
    blueSlider.value = defaults.floatForKey("Blue")
        
    if let dateObject = defaults.objectForKey("Date") {
        let date = dateObject as! NSDate
        dateLabel.text = "Previous save: " + formattedDate(date)
    }
        
    if let colorObject = defaults.objectForKey("Color") {
        let colorData = colorObject as! NSData
        let color = NSKeyedUnarchiver.unarchiveObjectWithData(colorData) as? UIColor
        view.backgroundColor = color
        updateColor(background: false)
    } else{
        updateColor(background: true)
    }
}

Add the function to viewDidload like this

override func viewDidLoad() {
    super.viewDidLoad()
    dateLabel.text = formattedDate(dateUsed)
    readDefaults()
}

Build and run. We don’t get the gray background with black lettering:

2016-02-26_07-38-05

Since the property list for NSUserDefaults hasn’t been created yet, the values for the keys were not found. Our UIColor wasn’t there and attempted to set the color from the three float values of the sliders. They weren’t there, and thus were 0.0,0.0,0.0, which is black. The dark text switch was false, and the index of our segments was 0, again because values were not found.

Change the color to orange by sliding the red all the way to the left and the green to 3/4 left. Click the Fourth segment in the segmented control and change the text to dark text.

2016-02-26_07-46-08

Click the Save Button. Double-Click the home button. If you are using the simulator that is Command-Shift-HH.

2016-02-26_07-49-31

Kill the app by dragging the app up. Then click the icon to start the app again. We get an orange screen and all of our other settings change the app, including the previous save date at the top

2016-02-26_07-53-56

Dealing With the Missing Keys

That first use of the app will run into some problems due to no values for the preferences. You might want to detect that case and set some of the preferences yourself. For example, we can change this in readDefaults:

 } else{
        updateColor(background: true)
}

to this:

} else{
            if blueSlider.value == 0 &&
               redSlider.value == 0 &&
                greenSlider.value == 0
            { // very probable no data in defaults
                blueSlider.value = 0.8
                greenSlider.value = 0.8
                redSlider.value = 0.8
                textColorSwitch.on = true
            }
            updateColor(background: true)
        }

We make an assumption if we don’t find the UIColor and the sliders are all zero, there is no preferences set, and so we set them programmatically to light gray with dark text. In the simulator click on the icon for the app, and delete it. Deleting the app deletes our preferences, as the alert tells you. Build and run the app again. This time we get a gray background.

2016-02-26_08-08-30

In most apps, It’s a good idea to have handlers available for missing data. There are three possibilities: there is missing data, the key is wrong, or the data type is wrong. For all three, you need to take some action.

Beyond the Settings View

Our app works for a single page. It’s more likely those defaults are for several pages. Close the app and in Xcode go to the storyboard. Click on the view controller and in the menu, select Editor>embed in>Navigation Controller. We get an navigation controller and a navigation bar in the view controller obscuring our stack view and a warning error. Click the resolver button resolver buttonAt the bottom of the storyboard and select Update All frames. The stack view will move to the correct place.
Add a bar button item to the right side of the navigation bar. Title the button Next. Drag a view controller to the story board. Connect the Next bar button to the new view controller by control dragging from the button to the view controller. Select a Show segue from the menu. You’ll end up with a storyboard that looks like this:

2016-02-26_08-38-19

Press Command-N to bring up the new file dialog box. Make a new Cocoa Touch Class named TwoViewController subclassing UIViewController with Swift as the language. In the code that appears for TwoViewController, Chge viewDidLoad to this:

override func viewDidLoad() {
    let defaults = NSUserDefaults.standardUserDefaults()
    super.viewDidLoad()
    if let colorObject = defaults.objectForKey("Color") {
        let colorData = colorObject as! NSData
        let color = NSKeyedUnarchiver.unarchiveObjectWithData(colorData) as? UIColor
        view.backgroundColor = color
    } else{
        view.backgroundColor = UIColor.lightGrayColor()
    }
}

We’ll set the background color from the defaults. If we don’t have a color, then we’ll set the background color to light gray.
Connect up this code by going back to the storyboard. Click our new view controller on the storyboard. In the identity inspector, set the view controller class to TwoViewController. Build and run. If you havent hit Save on the app since the last run you should have the light gray settings screen. Click Next and you get another light gray settings screen, since we dont have a key yet.

2016-02-26_08-53-59

Click back and change the color of the background with the sliders.

2016-02-26_08-54-34

Tap the Save button, then tap the next button.

2016-02-26_08-54-59

Now we have our background read from the defaults.

A Few Last Thoughts on NSUserDefaults.

There’s a lot more one could cover on NSUserDefaults. I need to add a few more suggestions about the topic. The first and most important is that NSUserDefaults and any property list is not secure. Don’t store sensitive information like passwords, financial data or health data here. It’s in XML. With not much work and access to a phone, it’s easily read.
Secondly, with NSData you might want to run more checks on the data than I did in our example. I didn’t check here to make sure colorData is a UIColor, which is something you might want to do.
With objects like UIColor, you may be adding a lot of code to a lot of view controllers to archive and unarchive them. It’s a good idea to extend NSUserDeafaults instead and make a function call to the extention in your view controllers.
Finally, I used a save method and required the user to press Save to save the defaults. There are times you may not want that, there are times you do. You could automatically save by placing the saving method call in viewWillDisappear or even in the actions for the controls themselves (thought that might be overkill). In an automatic save situation, the save will reflect on all other view controllers as they read the new defaults. Be careful. There are many cases both with automatic saves and manual saves the view controllers besides of settings won’t know they need to make changes. In our app you can change the color and hit Next and it will show the last color saved, not the color selected. A tab bar controller wont get the message on all its view controllers if the setting controller changes something. This is where notifications comes in. In our next persistence lesson, we’ll look at notifications while exploring a settings page outside your app in Apple’s Settings app.

The Whole Code

ViewController.swift

//
//  ViewController.swift
//  SwiftUserDefaultsDemo
//
//  Created by Steven Lipton on 2/24/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import UIKit

class ViewController: UIViewController {
    //MARK: Outlets and properties
    
    var dateUsed = NSDate()
    let defaults = NSUserDefaults.standardUserDefaults()
    
    @IBOutlet weak var dateLabel: UILabel!
    @IBOutlet weak var segmentedControl: UISegmentedControl!
    @IBOutlet weak var redSlider: UISlider!
    @IBOutlet weak var greenSlider: UISlider!
    @IBOutlet weak var blueSlider: UISlider!
    @IBOutlet weak var textColorSwitch: UISwitch!
    @IBOutlet weak var rgbLabel: UILabel!
    @IBOutlet weak var darkTextLabel: UILabel!
    
    
    //MARK: - Actions
    @IBAction func textColorSwitch(sender: UISwitch) {
        updateColor(background: true)
    }
    
    @IBAction func colorSliderChange(sender: UISlider) {
        updateColor(background: true)
    }
    
    @IBAction func saveButton(sender: UIButton) {
        //Storing simple types
        defaults.setBool(textColorSwitch.on, forKey: "DarkText")
        defaults.setInteger(segmentedControl.selectedSegmentIndex, forKey: "SegmentIndex")
        defaults.setFloat(redSlider.value, forKey: "Red")
        defaults.setFloat(greenSlider.value, forKey: "Green")
        defaults.setFloat(blueSlider.value, forKey: "Blue")
       
        
        //Storing a data object -- NSDate, Array, Dictionary, NSData, NSString, NSNumber
        defaults.setObject(NSDate(), forKey: "Date")
        
        //Storing the color and any other non-data object
        let color = view.backgroundColor!
        let colorData = NSKeyedArchiver.archivedDataWithRootObject(color)
        defaults.setObject(colorData, forKey: "Color")
    }
    //MARK: - Instance Methods
    func updateColor( background background:Bool){
        if background{
            view.backgroundColor = UIColor(
                red: CGFloat(redSlider.value),
                green: CGFloat(greenSlider.value),
                blue: CGFloat(blueSlider.value),
                alpha: 1.0)
        }
        var textColor = UIColor()
        if textColorSwitch.on{
            textColor = UIColor.blackColor()
        }else{
            textColor = UIColor.whiteColor()
        }
        dateLabel.textColor = textColor
        darkTextLabel.textColor = textColor
        rgbLabel.textColor = textColor
        segmentedControl.tintColor = textColor
        
        //update label for RGB color
        rgbLabel.text = String(
            format: "RGB: %1.2f, %1.2f %1.2f",
            redSlider.value,
            greenSlider.value,
            blueSlider.value)
        }
    func formattedDate(date:NSDate) -> String{
        let formatter = NSDateFormatter()
        formatter.timeStyle = .ShortStyle
        formatter.dateStyle = .MediumStyle
        return formatter.stringFromDate(date)
    }
    func readDefaults(){
        textColorSwitch.on = defaults.boolForKey("DarkText")
        segmentedControl.selectedSegmentIndex = defaults.integerForKey("SegmentIndex")
        redSlider.value = defaults.floatForKey("Red")
        greenSlider.value = defaults.floatForKey("Green")
        blueSlider.value = defaults.floatForKey("Blue")
        
        if let dateObject = defaults.objectForKey("Date") {
            let date = dateObject as! NSDate
            dateLabel.text = "Previous save: " + formattedDate(date)
        }
        
        if let colorObject = defaults.objectForKey("Color") {
            let colorData = colorObject as! NSData
            let color = NSKeyedUnarchiver.unarchiveObjectWithData(colorData) as? UIColor
            view.backgroundColor = color
            updateColor(background: false)
        } else{
            if blueSlider.value == 0 &&
                redSlider.value == 0 &&
                greenSlider.value == 0
            { // very probable no data in prefrences
                blueSlider.value = 0.8
                greenSlider.value = 0.8
                redSlider.value = 0.8
                textColorSwitch.on = true
            }
            updateColor(background: true)
        }

        
    }


    override func viewDidLoad() {
        super.viewDidLoad()
        dateLabel.text = formattedDate(dateUsed)
        readDefaults()
    }

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


}


TwoViewController.swift

//
//  TwoViewController.swift
//  SwiftUserDefaultsDemo
//
//  Created by Steven Lipton on 2/26/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import UIKit

class TwoViewController: UIViewController {
    
    override func viewDidLoad() {
        let defaults = NSUserDefaults.standardUserDefaults()
        super.viewDidLoad()
        if let colorObject = defaults.objectForKey("Color") {
            let colorData = colorObject as! NSData
            let color = NSKeyedUnarchiver.unarchiveObjectWithData(colorData) as? UIColor
            view.backgroundColor = color
        } else{
           view.backgroundColor = UIColor.lightGrayColor()
        }

    }

}