Beginning iPhone Development with Swift: Exploring the iOS SDK (2014)

Chapter 12. Application Settings and User Defaults

All but the simplest computer programs today have a preferences window where the user can set application-specific options. On Mac OS X, the Preferences… menu item is usually found in the application menu. Selecting it brings up a window where the user can enter and change various options. The iPhone and other iOS devices have a dedicated application called Settings, which you no doubt have played with any number of times. In this chapter, we’ll show you how to add settings for your application to the Settings application and how to access those settings from within your application.

Getting to Know Your Settings Bundle

The Settings application lets the user enter and change preferences for any application that has a settings bundle. A settings bundle is a group of files built in to an application that tells the Settings application which preferences the application wishes to collect from the user.

The Settings application acts as a common user interface for the iOS User Defaults mechanism. User Defaults is the part of the system that stores and retrieves preferences.

In an iOS application, User Defaults is implemented by the NSUserDefaults class. If you’ve done Cocoa programming on the Mac, you’re probably already familiar with NSUserDefaults because it is the same class that is used to store and read preferences on the Mac. You will have your applications use NSUserDefaults to read and store preference data using pairs of keys and values, just as you would access keyed data from a Dictionary. The difference is that NSUserDefaults data is persisted to the file system rather than stored in an object instance in memory.

In this chapter, we’re going to create an application, add and configure a settings bundle, and then access and edit those preferences from the Settings application and from within our own application.

One nice thing about the Settings application is that it provides a solution, so you don’t need to design your own user interface for your preferences. You create a property list describing your application’s available settings, and the Settings application creates the interface for you.

Immersive applications, such as games, generally should provide their own preferences view so that the user doesn’t need to quit to make a change. Even utility and productivity applications might, at times, have preferences that a user should be able to change without leaving the application. We’ll also show you to how to collect preferences from the user directly in your application and store those in iOS’s User Defaults.

One additional complication is that the user can actually switch to the Settings application, change a preference, and then switch back to your still-running application. We’ll show you how to handle that situation at the end of this chapter.

The Bridge Control Application

In this chapter, we’re going to build a simple application that keeps track of some aspects of managing the bridge of a starship, which I’m sure you’ll agree is a useful enterprise. Our first step will be to create a settings bundle so that, when the user launches the Settings application, there will be an entry for our application, Bridge Control (see Figure 12-2).

Figure 12-2. The Settings application, which shows an entry for our Bridge Control application in the simulator

If the user selects our application, Settings will drill down into a view that shows the preferences relevant to our application. As you can see in Figure 12-3, the Settings application uses text fields, secure text fields, switches, and sliders to coax values out of our intrepid user.

Figure 12-3. Our application’s primary settings view

Also notice the two items in the view that have disclosure indicators. The first one, Rank, takes the user to another table view that displays the options available for that item. From that table view, the user can select a single value (see Figure 12-4).

Figure 12-4. Selecting a single preference item from a list

The More Settings disclosure indicator allows the user to drill down to another set of preferences (see Figure 12-5). This child view can have the same kinds of controls as the main settings view and can even have its own child views. You may have noticed that the Settings application uses a navigation controller, which it needs because it supports the construction of hierarchical preference views.

Figure 12-5. A child settings view for our application

When users launch our application, they will be presented with a list of the preferences gathered in the Settings application (see Figure 12-6).

Figure 12-6. Our application’s main view

To show how to update preferences from within our application, we also provide a second view where they can change additional preferences directly in the application (see Figure 12-7).

Figure 12-7. Setting some preferences directly in our application

Let’s get started building Bridge Control, shall we?

Creating the Project

In Xcode, press N or select FileNew Project…. When the new project assistant comes up, select Application from under the iOS heading in the left pane, click the Tabbed Application icon, and then click Next. On the next screen, name your project Bridge Control. Set Devices to Universal, and then click the Next button. Finally, choose a location for your project and click Create.

The Bridge Control application is based on the UITabBarController class that we used in Chapter 7. The template creates two tabs, which is all we’ll need. Each tab requires an icon. You’ll find these in the 12 – Images folder in the example source code archive. In Xcode, selectImages.xcassets, and then drag the singleicon.imageset and doubleicon.imageset folders from 12 – Images into the editing area.

Next, we’ll assign the icons to their tab bar items. Select Main.storyboard and you’ll see the tab bar controller and the two child controllers for its tabs, one labeled First View, the other Second View. Select the first child controller, and then click its tab bar item, which currently shows a round circle and the title First. In the Bar Item section of the Attributes Inspector, change the Title to Main and the Image to singleicon, as shown in Figure 12-8. Now select the tab bar item for the second child controller and change the title from Second to Settings and the image from second todoubleicon. Finally, select Images.xcassets again and delete the first and second image sets that the template created—we don’t need them anymore. That’s enough work on the application itself for now—before doing anything more, let’s create its settings bundle.

Figure 12-8. Setting the icon for the first tab bar item

Working with the Settings Bundle

The Settings application uses the contents of each application’s settings bundle to construct a settings view for that application. If an application has no settings bundle, then the Settings app doesn’t show anything for it. Each settings bundle must contain a property list called Root.plist that defines the root-level preferences view. This property list must follow a very precise format, which we’ll talk about when we set up the property list for our app’s settings bundle.

When the Settings application starts up, it checks each application for a settings bundle and adds a settings group for each application that includes a settings bundle. If we want our preferences to include any subviews, we need to add property lists to the bundle and add an entry to Root.plistfor each child view. You’ll see exactly how to do that in this chapter.

Adding a Settings Bundle to Our Project

In the Project Navigator, click the Bridge Control folder, and then select FileNew File… or press N. In the left pane, select Resource under the iOS heading, and then select the Settings Bundle icon (see Figure 12-9). Click the Next button, leave the default name of Settings.bundle, and click Create.

Figure 12-9. Creating a settings bundle in Xcode

You should now see a new item in the project window called Settings.bundle. Expand the Settings.bundle item, and you should see two subitems: a folder named en.lproj, containing a file named Root.strings, and another named Root.plist. We’ll discuss en.lproj in Chapter 22 when we talk about localizing your application into other languages. Here, we’ll concentrate on Root.plist.

Setting Up the Property List

Select Root.plist and take a look at the editor pane. You’re looking at Xcode’s property list editor (see Figure 12-10).

Notice the organization of the items in the property list. Property lists are essentially dictionaries, storing item types and values and using a key to retrieve them, just as a Dictionary does.

Several different types of nodes can be put into a property list. The Boolean, Data, Date, Number, and String node types are meant to hold individual pieces of data, but you also have a couple of ways to deal with whole collections of nodes, as well. In addition to Dictionary node types, which allow you to store other dictionaries, there are Array nodes, which store an ordered list of other nodes similar to an Array. The Dictionary and Array types are the only property list node types that can contain other nodes.

Note Although you can use most kinds of objects as keys in a Dictionary, keys in property list dictionary nodes must be strings. However, you are free to use any node type for the values.

When creating a settings property list, you need to follow a very specific format. Fortunately, Root.plist, the property list that came with the settings bundle you just added to your project, follows this format exactly. Let’s take a look.

In the Root.plist editor pane, names of keys can either be displayed in their true, “raw” form or in a slightly more human-readable form. We’re big fans of seeing things as they truly are whenever possible, so right-click anywhere in the editor and make sure the Show Raw Keys/Values option in the contextual menu is checked (see Figure 12-11). The rest of our discussion here uses the real names for all the keys we’re going to talk about, so this step is important.

Figure 12-11. Control-click anywhere in the property list editing pane and make sure the Show Raw Keys/Values item is checked. This will ensure that real names are used in the property list editor, which makes your editing experience more precise

Caution At the time of writing, leaving the property list, either by editing a different file or by quitting Xcode, resets the Show Raw Keys/Values item to be unchecked. If your text suddenly looks a little different, take another look at that menu item and make sure it is checked.

One of the items in the dictionary is StringsTable. A strings table is used in translating your application into another language. We’ll discuss the translation of strings in Chapter 22 when we get into localization. We won’t be using it in this chapter, but feel free to leave it in your project since it won’t do any harm.

In addition to StringsTable, the property list contains a node named PreferenceSpecifiers, which is an array. This array node is designed to hold a set of dictionary nodes, where each node represents either a single preference item that the user can modify or a single child view that the user can drill down into.

Click the disclosure triangle to the left of PreferenceSpecifiers to expand that node. You’ll notice that Xcode’s template kindly gave us four child nodes (see Figure 12-12). Those nodes aren’t likely to reflect our actual preferences, so delete Item 1, Item 2, and Item 3 (select each one and press the Delete key, one after another), leaving just Item 0 in place.

Figure 12-12. Root.plist in the editor pane, this time with PreferenceSpecifiers expanded

Note To select an item in the property list, it is best to click one side or the other of the Key column to avoid bringing up the Key column’s drop-down menu.

Single-click Item 0 but don’t expand it. Xcode’s property list editor lets you add rows simply by pressing the Return key. The current selection state—including which row is selected and whether it’s expanded—determines where the new row will be inserted. When an unexpanded array or dictionary is selected, pressing Return adds a sibling node after the selected row. In other words, it will add another node at the same level as the current selection. If you were to press Return (but don’t do that now), you would get a new row called Item 1 immediately after Item 0. Figure 12-13 shows an example of hitting Return to create a new row. Notice the drop-down menu that allows you to specify the kind of preference specifier this item represents—more on this in a bit.

Figure 12-13. We selected Item 0 and hit Return to create a new sibling row. Note the drop-down menu that appears, allowing us to specify the kind of preference specifier this item represents

Now expand Item 0 and see what it contains (see Figure 12-14). The editor is now ready to add child nodes to the selected item. If you were to press Return at this point (again, don’t actually press it now), you would get a new first child row inside Item 0.

Figure 12-14. When you expand Item 0, you’ll find a row with a key of Type and a second row with a key of Title. This represents a group with a title of Group

One of the items inside Item 0 has a key of Type. Every property list node in the PreferenceSpecifiers array must have an entry with this key. The Type key is typically the second entry, but order doesn’t matter in a dictionary, so the Type key doesn’t need to be second. The Type key tells the Settings application what type of data is associated with this item.

In Item 0, the Type item has a value of PSGroupSpecifier. This indicates that the item represents the start of a new group. Each item that follows will be part of this group—until the next item with a Type of PSGroupSpecifier.

If you look back at Figure 12-3, you’ll see that the Settings application presents the application settings in a grouped table. Item 0 in the PreferenceSpecifiers array in a settings bundle property list should always be a PSGroupSpecifier, so that the settings start in a new group. This is important because you need at least one group in every Settings table.

The only other entry in Item 0 has a key of Title, and this is used to set an optional header just above the group that is being started.

Now take a closer look at the Item 0 row itself, and you’ll see that it’s actually shown as Item 0 (Group – Group). The values in parentheses represent the value of the Type item (the first Group) and the Title item (the second Group). This is a nice shortcut that Xcode gives you so that you can visually scan the contents of a settings bundle.

As shown back in Figure 12-3, we called our first group General Info. Double-click the value next to Title, and change it from Group to General Info (see Figure 12-15). When you enter the new title, you may notice a slight change to Item 0. It’s now shown as Item 0 (Group – General Info)to reflect the new title. In the Settings application, the title is shown in uppercase, so the user will actually see GENERAL INFO instead. You can see this in Figure 12-3.

Figure 12-15. We changed the title of the Item 0 group from Group to General Info

Adding a Text Field Setting

We now need to add a second item in this array, which will represent the first actual preference field. We’re going to start with a simple text field.

If you single-click the PreferenceSpecifiers row in the editor pane (don’t do this, just keep reading) and press Return to add a child, the new row will be inserted at the beginning of the list, which is not what we want. We want to add a row at the end of the array.

To add the row, click the disclosure triangle to the left of Item 0 to close it, and then select Item 0 and press Return. This gives you a new sibling row after the current row (see Figure 12-16). As usual, when the item is added, a drop-down menu appears, showing the default value of Text Field.

Figure 12-16. Adding a new sibling row to Item 0

Click somewhere outside the drop-down menu to make it go away, and then click the disclosure triangle next to Item 1 to expand it. You’ll see that it contains a Type row set to PSTextFieldSpecifier. This is the Type value used to tell the Settings application that we want the user to edit this setting in a text field. It also contains two empty rows for Title and Key (see Figure 12-17).

Figure 12-17. Our text field item, expanded to show the type, title, and key

Select the Title row, and then double-click in the whitespace of the Value column. Type in Commanding Officer to set the Title value. This is the text that will appear in the Settings app.

Now do the same for the Key row (no, that’s not a misprint, you’re really looking at a key called Key). For a value, type in officer (note the lowercase first letter). Remember that user defaults work like a Dictionary. This entry tells the Settings application which key to use when it stores the value entered in this text field.

Recall what we said about NSUserDefaults? It lets you store values using a key, similar to a Dictionary. Well, the Settings application will do the same thing for each of the preferences it saves on your behalf. If you give it a key value of foo, then, later in your application, you can request the value for foo, and it will give you the value the user entered for that preference. We will use this same key value later to retrieve this setting from the user defaults in our application.

Note Our Title has a value of Commanding Officer and our Key has a value of officer. This uppercase/lowercase difference will happen frequently, and here we’re even compounding the difference by using two words for the displayed title, and a single word for the key. The Title is what appears on the screen; so the capital C and O, and putting a space between the words, all makes sense. The Key is a text string we’ll use to retrieve preferences from the user defaults, so all lowercase makes sense there. Could we use all lowercase for Title? You bet. Could we use all capitals for Key? Sure! As long as you capitalize it the same way when you save and when you retrieve, it doesn’t matter which convention you use for your preference keys.

Now select the last of the three Item 1 rows (the one with a Key of Key) and press Return to add another entry to the Item 1 dictionary, giving this one a key of AutocapitalizationType. Note that, as soon as you start typing AutocapitalizationType, Xcode presents you with a list of matching choices, so you can simply pick one from the list instead of typing the whole name. After you’ve entered AutocapitalizationType, press the Tab key or click the small up/down arrow icon on the right of the Value column to open a list where you can select from the available options. ChooseWords. This specifies that the text field should automatically capitalize each word that the user types in this field.

Create one last new row and give it a key of AutocorrectionType and a value of No. This will tell the Settings application not to autocorrect values entered into this text field. In any situation where you do want the text field to use autocorrection, you would set the value in this row to Yes. Again, Xcode presents you with a list of matching choices as you begin entering AutocorrectionType, and it shows you a list of valid options in a pop-up.

When you’re finished, your property list should look like the one shown in Figure 12-18.

Figure 12-18. The finished text field specified in Root.plist

Adding an Application Icon

Before we try our new setting, let’s add an application icon to the project. You’ve done this before.

Save Root.plist, the property file you just edited. Next, use the Project Navigator to select the Images.xcassets item, and then select the AppIcon item it contains. There, you’ll find a set of drop targets where icons can be placed.

In the Finder, navigate first to the source code archive, and then into the 12 – Images folder. Drag the file SettingsIcon.png into the iPad Settings 1x slot of the Images.xcassets editor in Xcode, and drag SettingsIcon@2x.png into the iPhone Settings 2x and iPad Settings 2x slots. While you’re here, let’s add icons for the application itself. Drag AppIcon-iPhone@2x.png onto the iPhone App slot, AppIcon-iPad.png onto the iPad App 1x slot, and AppIcon-iPad@2x.png onto the iPad App 2x slot. When you’re done, the editor should look like Figure 12-19.

Figure 12-19. Adding the settings and app icons for our application

That’s it. Now compile and run the application by selecting ProductRun. You haven’t built any sort of GUI for the app yet, so you’ll just see the first tab of the tab bar controller. Press the Home button, and then tap the icon for the Settings application. You will find an entry for our application, which uses the icon added earlier (see Figure 12-2). Click the Bridge Control row, and you will be presented with a simple settings view with a single text field, as shown in Figure 12-20.

Figure 12-20. Our root view in the Settings application after adding a group and a text field

Quit the simulator and go back to Xcode. We’re not finished yet, but you should now have a sense of how easy it is to add preferences to your application. Let’s add the rest of the fields for our root settings view. The first one we’ll add is a secure text field for the user’s authorization code.

Adding a Secure Text Field Setting

Click Root.plist to return to your setting specifiers (don’t forget to turn on Show Raw Keys/Values, assuming Xcode’s editing area has reset this). Collapse Item 0 and Item 1, and then select Item 1. Press C to copy it to the clipboard, and then press V to paste it back. This will create a new Item 2 that is identical to Item 1. Expand the new item and change the Title to Authorization Code and the Key to authorizationCode. Remember that the Title is what’s shown in an on-screen label, and the Key is what’s used for saving the value.

Next, add one more child to the new item. Remember that the order of items does not matter, so feel free to place it directly below the Key item you just edited. To do this, select the Key/authorizationCode row, and then hit Return.

Give the new item a Key of IsSecure (note the leading uppercase I) and press Tab, and you’ll see that Xcode automatically changes the Type to Boolean. Now change its Value from NO to YES, which tells the Settings application that this field needs to hide the user’s input like a password field, rather than behaving like an ordinary text field. Finally, change AutocapitalizationType to None. Our finished Item 2 is shown in Figure 12-21.

Figure 12-21. Our finished Item 2, a text field designed to accept an authorizationCode

Adding a Multivalue Field

The next item we’re going to add is a multivalue field. This type of field will automatically generate a row with a disclosure indicator. Clicking it will let users drill down to another table, where they can select one of several rows.

Collapse Item 2, select the row, and then press Return to add Item 3. Use the pop-up attached to the Key field to select Multi Value, and then expand Item 3 by clicking the disclosure triangle.

The expanded Item 3 already contains a few rows. One of them, the Type row, is set to PSMultiValueSpecifier. Look for the Title row and set its value to Rank. Then find the Key row and give it a value of rank. The next part is a little tricky, so let’s talk about it before we do it.

We’re going to add two more children to Item 3, but they will be Array type nodes, not String type nodes, as follows:

· One array, called Titles, will hold a list of the values from which the user can select.

· The other array, called Values, will hold a list of the values that are stored in the user defaults.

So, if the user selects the first item in the list, which corresponds to the first item in the Titles array, the Settings application will actually store the first value from the Values array. This pairing of Titles and Values lets you present user-friendly text to the user, but actually stores something else, like a number, date, or different string.

Both of these arrays are required. If you want them to be the same, you can create one array, copy it, paste it back in, and then change the key so that you have two arrays with the same content, but stored under different keys. We’ll actually do just that.

Select Item 3 (leave it open) and press Return to add a new child. You’ll see that, once again, Xcode is aware of the type of file we’re editing and even seems to anticipate what we want to do: the new child row already has its Key set to Titles and is configured to be an Array, which is just what we wanted! Press Return to stop editing the Key field, and then expand the Titles row and hit Return to add a child node. Repeat this five more times, so you have a total of six child nodes. All six nodes should be String type and should be given the following values: Ensign,Lieutenant, Lieutenant Commander, Commander, Captain, and Commodore.

Once you’ve created all six nodes and entered their values, collapse Titles and select it. Next, press C to copy it and press V to paste it back. This will create a new item with a key of Titles - 2. Double-click the key Titles - 2 and change it to Values.

We’re almost finished with our multivalue field. There’s just one more required value in the dictionary, which is the default value. Multivalue fields must have one—and only one—row selected. So, we need to specify the default value to be used if none has yet been selected, and it needs to correspond to one of the items in the Values array (not the Titles array, if they are different). Xcode already added a DefaultValue row when we created this item, so all we need to do now is give it a value of Ensign. Go ahead and do that now. Figure 12-22 shows our finalized version of Item 3.

Figure 12-22. Our finished Item 3, a multivalue field designed to let the user select from one of five possible values

Let’s check our work. Save the property list, and build and run the application again. When your application starts, press the Home button and launch the Settings application. When you select Bridge Control, you should see three fields on your root-level view (see Figure 12-23). Go ahead and play with your creation, and then let’s move on.

Figure 12-23. Three fields down. Not too shabby!

Adding a Toggle Switch Setting

The next item we need to get from the user is a Boolean value that indicates whether our warp engines are turned on. To capture a Boolean value in our preferences, we are going to tell the Settings application to use a UISwitch by adding another item to our PreferenceSpecifiers array with a type of PSToggleSwitchSpecifier.

Collapse Item 3 if it’s currently expanded, and then single-click it to select it. Press Return to create Item 4. Use the drop-down menu to select Toggle Switch, and then click the disclosure triangle to expand Item 4. You’ll see there’s already a child row with a Key of Type and a Value ofPSToggleSwitchSpecifier. Give the empty Title row a value of Warp Drive and set the value of the Key row to warp.

We have one more required item in this dictionary, which is the default value. Just as with the Multi Value setup, here Xcode has already created a DefaultValue row for us. Let’s turn on our warp engines by default by giving the DefaultValue row a value of YES. Figure 12-24 shows our completed Item 4.

The next item we need to implement is a slider. In the Settings application, a slider can have a small image at each end, but it can’t have a label. Let’s put the slider in its own group with a header, so that the user will know what the slider does.

Start by collapsing Item 4. Now single-click Item 4 and press Return to create a new row. Use the pop-up to turn the new item into a Group, and then click the item’s disclosure triangle to expand it. You’ll see that Type is already set to PSGroupSpecifier. This will tell the Settings application to start a new group at this location. Double-click the value in the row labeled Title and change the value to Warp Factor.

Collapse Item 5 and select it, and then press Return to add a new sibling row. Use the pop-up to change the new item into a Slider, which indicates to the Settings application that it should use a UISlider to get this information from the user. Expand Item 6 and set the value of the Key row to warpFactor, so that the Settings application knows which key to use when storing this value.

We’re going to allow the user to enter a value from 1 to 10, and we’ll set the default to warp 5. Sliders need to have a minimum value, a maximum value, and a starting (or default) value; and all of these need to be stored as numbers, not strings, in your property list. Fortunately, Xcode has already created rows for all these values. Give the DefaultValue row a value of 5, the MinimumValue row a value of 1, and the MaximumValue row a value of 10.

If you want to test the slider, go ahead, but hurry back. We’re going to do just a bit more customization.

As noted, you can place an image at each end of the slider. Let’s provide little icons to indicate that moving the slider to the left slows us down and moving it to the right speeds us up.

Adding Icons to the Settings Bundle

In the 12 – Images folder in the project archive that accompanies this book, you’ll find two icons called rabbit.png and turtle.png. We need to add both of these to our settings bundle. Because these images need to be used by the Settings application, we can’t just put them in our Bridge Control folder; we need to put them in the settings bundle, so the Settings application can access them.

To do that, find the Settings.bundle in the Project Navigator. We’ll need to open this bundle in the Finder. Control-click the Settings.bundle icon in the Project Navigator. When the contextual menu appears, select Show in Finder (see Figure 12-25) to show the bundle in the Finder.

Figure 12-25. The Settings.bundle contextual menu

Remember that bundles look like files in the Finder, but they are really folders. When the Finder window opens to show the Settings.bundle file, Control-click the file and select Show Package Contents from the contextual menu that appears. This will open the settings bundle in a new Finder window, and you should see the same two items that you see in Settings.bundle in Xcode. Copy the two icon files, rabbit.png and turtle.png, from the 12 – Images folder into the Settings.bundle package contents in the Finder window, next to en.proj and Root.plist.

You can leave this window open in the Finder, as we’ll need to copy another file here soon. Now we’ll return to Xcode and tell the slider to use these two images.

Back in Xcode, return to Root.plist and add two more child rows under Item 6. Give one a key of MinimumValueImage and a value of turtle. Give the other a key of MaximumValueImage and a value of rabbit. Figure 12-26 shows our finished Item 6.

Save your property list, and then build and run the app to make sure everything is still hunky-dory. You should be able to navigate to the Settings application and find the slider waiting for you, with the sleepy turtle and the happy rabbit at their respective ends (see Figure 12-27).

Figure 12-27. We have text fields, multivalue fields, a toggle switch, and a slider. We’re almost finished

Adding a Child Settings View

We’re going to add another preference specifier to tell the Settings application that we want it to display a child settings view. This specifier will present a row with a disclosure indicator that, when tapped, will take the user down to a whole new view full of preferences. Let’s get to it.

Since we don’t want this new preference to be grouped with the slider, first we’ll copy the group specifier in Item 0 and paste it at the end of the PreferenceSpecifiers array to create a new group for our child settings view.

In Root.plist, collapse all open items, and then single-click Item 0 to select it, and press C to copy it to the clipboard. Next, select Item 6, and then press V to paste in a new Item 7. Expand Item 7 and double-click the Value column next to the key Title, changing it from General Info toAdditional Info.

Now collapse Item 7 again. Select it and press Return to add Item 8, which will be our actual child view. Expand it by clicking the disclosure triangle. Find the Type row, give it a value of PSChildPaneSpecifier, and then set the value of the Title row to More Settings.

We need to add one final row to Item 8, which will tell the Settings application which property list to load for the More Settings view. Add another child row, and give it a key of File (you can do this by changing the key of the last row in the group from Key to File) and a value of More (seeFigure 12-28). The file extension .plist is assumed and must not be included (if it is, the Settings application won’t find the .plist file).

Figure 12-28. Our finished Items 7 and 8, setting up the new Additional Info settings group and providing the child pane link to the file, More.plist

We are adding a child view to our main preference view. The settings in that child view are specified in the More.plist file. We need to copy More.plist into the settings bundle. We can’t add new files to the bundle in Xcode, and the Property List Editor’s Save dialog will not let us save into a bundle. So, we need to create a new property list, save it somewhere else, and then drag it into the Settings.bundle window using the Finder.

You’ve now seen all the different types of preference fields that you can use in a settings bundle .plist file. To save yourself some typing, you can grab More.plist out of the 12 – Images folder in the project archive that accompanies this book, and then drag it into that Settings.bundle window we left open earlier, alongside Root.plist.

Tip When you create your own child settings views, the easiest approach is to make a copy of Root.plist and give it a new name. Next, delete all of the existing preference specifiers except the first one and add whatever preference specifiers you need for that new file.

We’re finished with our settings bundle. Feel free to compile, run, and test the Settings application. You should be able to reach the child view and set values for all the other fields. Go ahead and play with it, and make changes to the property list if you want.

Tip We’ve covered almost every configuration option available (at least at the time of this writing). You can find the full documentation of the settings property list format in the document called Settings Application Schema Reference in the iOS Dev Center. You can get that document, along with a ton of other useful reference documents, from this page: http://developer.apple.com/library/ios/navigation/.

Before continuing, select the Images.xcassets item in Xcode’s Project Navigator, and then copy the rabbit.png and turtle.png icons from the 12 – Images folder in the project archive into the left side of the editor area. This will add these icons to the project as new images resources, ready for use. We’ll use them in our application to show the value of the current settings.

You might have noticed that the two icons you just added are exactly the same ones you added to your settings bundle earlier, and you might be wondering why. Remember that iOS applications can’t read files out of other applications’ sandboxes. The settings bundle doesn’t become part of our application’s sandbox—it becomes part of the Settings application’s sandbox. Since we also want to use those icons in our application, we need to add them separately to our Bridge Control folder, so they are copied into our application’s sandbox, as well.

Reading Settings in Our Application

We’ve now solved half of our problem. The user can use the Setting app to declare their preferences, but how do we get to them from within our application? As it turns out, that’s the easy part.

Retrieving User Settings

We’ll use a class called NSUserDefaults to access the user’s settings. NSUserDefaults is implemented as a singleton, which means there is only one instance of NSUserDefaults running in our application. To get access to that one instance, we call the class methodstandardUserDefaults, like so:

let defaults = NSUserDefaults.standardUserDefaults()

Once we have a pointer to the standard user defaults, we use it much like a Dictionary. To get a value from it, we can call objectForKey:, which will return an object, a String or a Foundation object such as NSDate, or NSNumber. If we want to retrieve the value as a scalar—like an int, float, or Boolean—we can use another method, such as intForKey(), floatForKey(), or boolForKey().

When you were creating the property list for this application, you added an array of PreferenceSpecifiers inside a .plist file. Within the Settings application, some of those specifiers were used to create groups, while others were used to create interface objects for user interaction. Those are the specifiers we are really interested in because they hold the keys the real settings data. Every specifier that was tied to a user setting has a Key named Key. Take a minute to go back and check. For example, the Key for our slider has a value of warpFactor. The Key for our Authorization Code field is authorizationCode. We’ll use those keys to retrieve the user settings.

Instead of using strings for each key directly in our methods, we’ll define some constants for those values. That way we can use these constants in our code instead of inline strings, where we would run the risk of mistyping something. We’ll set these up in a separate Swift file, since we’re going to use some of them in more than one class later on. So, in Xcode, press N and, from the iOS section of the file creation window, choose Source and then Swift File. Press Next, call the file Constants.swift and press Create. Open the newly created file and add these bold lines:

These constants are the keys that we used in our .plist file for the different preference fields. Now that we have a place to display the settings, let’s quickly set up our main view with a bunch of labels. Before going over to Interface Builder, let’s create outlets for all the labels we’ll need. Single-click FirstViewController.swift, and make the following changes:

There’s nothing new here—we declare nine properties, all of them labels with the @IBOutlet keyword to make them connectable in Interface Builder.

Save your changes. Now that we have our outlets declared, let’s head over to the storyboard file to create the GUI.

Creating the Main View

Select Main.storyboard to edit it in Interface Builder. When it comes up, you’ll see the tab bar view controller on the left and the view controllers for the two tabs on the right, one above the other. The upper one is for the first tab, corresponding to the FirstViewController class, and the lower one is for the second tab, which will be implemented in the SecondViewController class.

We’re going to start by adding a bunch of labels to the View of FirstViewController, so it looks like the one shown in Figure 12-29. We’ll need a grand total of 18 labels. Half of them, on the left side of the screen, will be right-aligned and bold; the other half, on the right side of the screen, will be used to display the actual values retrieved from the user defaults and will have outlets pointing to them. All of the changes that we make here will be to the view controller for the first tab, which is the upper one on the right of the storyboard.

Figure 12-29. The view controller for the first tab in Interface Builder, showing the 18 labels we added

Start by expanding the node for Main Scene in the Document Outline, and then expand the View item. You’ll find three child views already in place—delete them all. Next, rename the View item to Main View. Now drag a Label from the Object Library and drop it near the top left of the view. Drag it all the way to the left of the window (or at least to the left blue guideline), and then widen it by dragging its right edge toward the center of the view, like the Officer label in Figure 12-29. In the Attributes Inspector, make the text right aligned and change the font to System Bold 15. Now Option-drag the label downward to create eight more copies, lining them up neatly to form the left column. Change the label texts so that they match the ones in Figure 12-29.

Building the right-hand column is slightly easier. Drag another label onto the View and place it to the right of the Officer label, leaving a small gap between them. In the Attributes Inspector, set the font to System 15. Option-drag this label downward to create eight more copies, each of them lined up with the corresponding label in the left column.

Now we need to set the auto layout constraints. Let’s start by linking the top two labels together. Control-drag from the Officer label to the label to its right. Release the mouse and hold down Shift. In the pop-up menu, select Horizontal Spacing and Baseline, and then click outside the pop-up. Do the same for the other eight rows, to link each pair of labels together.

Next, we’ll fix the positions of the labels in the left column relative to the left and top of the view. In the Document Outline, Control-drag from the Officer label to Main View. Release the mouse, hold down the Shift key and select Leading Space to Container Margin and Top Space to Top Layout Guide, and then click outside the pop-up to apply the constraints. Do the same with the other eight labels in the left column.

Finally, we need to fix the widths of the labels in the left column. Select the Officer label and click the Pin button below the storyboard editor. In the pop-up, check the Width check box followed by Add 1 Constraint. Repeat this process for all of the labels in the left column.

All of the labels should now be properly constrained, so select the view controller First in the Document Outline, and then click the Resolve Auto Layout Issues button underneath the storyboard editor and select Update Frames (if this option is not enabled, all of the labels are already in their correct positions in the storyboard). If all is well, the labels will move to their final positions.

The next thing we need to do is link the labels in the right column to their outlets. Open FirstViewController.swift in the Assistant Editor and Control-drag from the top label in the right column to the officerLabel outlet to connect them. Control-drag from the second label in the right column to authorizationLabel, and repeat until all nine labels in the right column are connected to their outlets. Save the Main.storyboard file.

Updating the First View Controller

In Xcode, select FirstViewController.swift and add the following code at the bottom of the class:

There’s not really much here that should throw you. The refreshFields() method does two things. First, it grabs the standard user defaults. Second, it sets the text property of all the labels to the appropriate object from the user defaults using the same key values that we put in our .plistfile. Notice that for warpFactorLabel, we’re calling stringValue on the object returned. Most of our other preferences are strings, which come back from the user defaults as String objects. The preference stored by the slider, however, comes back as an NSNumber, but we need a string for display purposes, so we call stringValue on it to get a string representation of the value it holds.

After that, we overrode our superclass’s viewWillAppear() method, and there we called our refreshFields() method. This causes the values that the user sees to be updated whenever the view appears—which includes when the application starts and when the user switches from the second tab to the first tab.

If you run the application at this point, you should see the user interface that you built for the first tab, but some or all of the fields will be empty. Don’t worry, this is not a bug. It is correct behavior, believe it or not. You’ll see why, and how to fix it, in the upcoming “Registering Default Values” section.

Changing Defaults from Our Application

Now that we have the main view up and running, let’s build the second tab. As you can see in Figure 12-30, the second tab features our warp drive switch, as well as the warp factor slider. We’ll use the same controls that the Settings application uses for these two items: a switch and a slider. In addition to declaring our outlets, we’ll also declare a method called refreshFields(), just as we did in FirstViewController, and two action methods that will be triggered by the user touching the controls.

Figure 12-30. Designing the second view controller in Interface Builder

Now, save your changes and select Main.storyboard to edit the GUI in Interface Builder, this time focusing on the Settings Scene in the Document Outline. Hold down the Option key and click the disclosure triangle to expand Settings Scene and everything below it. Change the name of theView item to Main View and delete all of its child nodes.

Next, select Main View in the Settings Scene in the Document Outline, and then bring up the Attributes Inspector. Change the background color by using the Background pop-up to select Light Gray Color.

Next, drag two labels from the library and place them on Main View in the storyboard. Make sure you drag them onto the Settings Scene controller, which is the one at the bottom right of the storyboard. Double-click one of them, and change it to read Warp Engines:. Double-click the other, and call it Warp Factor:. Place both labels against the left guideline, one above the other. You can use Figure 12-30 as a placement guide.

Next, drag over a Switch from the library and place it against the right side of the view, across from the label that reads Warp Engines. Control-drag from the View Controller icon at the top of the Settings Scene to the new switch and connect it to the engineSwitch outlet. Next, openSecondViewController in the Assistant Editor and Control-drag from the switch to a point just above the @end line at the bottom of the file. Release the mouse and create an Action called engineSwitchTapped, leaving all the other selections in the pop-up at their default values.

Drag over a Slider from the library and place it below the label that reads Warp Factor:. Resize the slider so that it stretches from the blue guideline on the left margin to the one on the right. Now Control-drag from the View Controller icon at the top of the Settings Scene to the slider, and then connect it to the warpFactorSlider outlet. Next, Control-drag from the slider to the end of the SecondViewController class and create an Action called warpSliderTouched, leaving all the other selections in the pop-up at their default values.

Single-click the slider if it’s not still selected and bring up the Attributes Inspector. Set Minimum to 1.00, Maximum to 10.00, and Current to 5.00. Next, select turtle for Min Image and rabbit for Max Image. If those don't show up in the pop-up buttons, make sure you dragged the images into the Images.xcassets assets catalog.

To complete the user interface, drag a button from the Object Library, drop it at the bottom of the view, and change its name to Open Settings Application. Control-drag from the button to just below the warpSliderTouched method in SecondViewController and create an Actioncalled settingsButtonClicked. We’ll use this button at the end of the chapter.

It’s time to add the auto layout constraints. Start by selecting Main.storyboard. In the Document Outline, Control-drag from the Warp Engines label to Main View and release the mouse. Hold down Shift and select Leading Space to Container Margin and Top Space to Top Layout Guide, and then click outside the pop-up to apply the constraints. Repeat this for the Warp Factor label.

Next, Control-drag from the switch to Main View and release the mouse. Hold down Shift and select Trailing Space to Container Margin and Top Space to Top Layout Guide, and then click outside the pop-up. Control-drag from the slider to Main View and release the mouse. Hold down Shift and this time select Leading Space to Container Margin, Trailing Space to Container Margin and Top Space to Top Layout Guide, and then click outside the pop-up to apply the constraints.

Finally, we need to fix the position of the button at the bottom of the view. Control-drag from the button to Main View, release the mouse and select Bottom Space to Bottom Layout Guide and Center Horizontally in Container while holding down the Shift key, and then click anywhere outside the pop-up. That completes the auto layout constraints.

Now, let’s finish the settings view controller. Select SecondViewController.swift and add the following code at the bottom of the class:

When the view controller’s view appears (e.g., when the tab is selected), we call our refreshFields() method. This method’s three lines of code get a reference to the standard user defaults, and then use the outlets for the switch and slider to make them display the values stored in the user defaults. We also implemented the engineSwitchTapped() and warpSliderTouched() action methods, so that we could stuff the values from our controls back into the user defaults when the user changes them.

Now you should be able to run the app, switch to the second tab, edit the values presented there, and see them reflected in the first tab when you switch back.

Registering Default Values

We’ve created a settings bundle, including some default settings for a few values, to give the Settings app access to our app’s preferences. We’ve also set up our own app to access the same information, with a GUI to let the user see and edit it. However, one piece is missing: our app is completely unaware of the default values specified in the settings bundle. You can see this for yourself by deleting the Bridge Control app from the iOS simulator or the device you’re running on (thereby deleting the preferences stored for the app), and then running it from Xcode again. At the start of a fresh launch, the app will show you blank values for all the settings. Even the default values for the warp drive settings, which we defined in the settings bundle, are nowhere to be seen. If you then switch over to the Settings app, you’ll see the default values; however, unless you actually change the values there, you’ll never see them back in the Bridge Control app!

The reason our default settings disappeared is that our app knows nothing about the settings bundle it contains. So, when it tries to read the value from NSUserDefaults for warpFactor and finds nothing saved under that key, it has nothing to show us. Fortunately, NSUserDefaultsincludes a method called registerDefaults() that lets us specify the default values that we should find if we try to look up a key/value that hasn’t been set. To make this work throughout the app, it’s best if this is called early during app start-up. Select AppDelegate.swift and modify theapplication(_, didFinishLaunchingWithOptions:) method:

The first thing we do here is create a dictionary that contains three key/value pairs, one for each of the keys available in Settings that requires a default value. We’re using the same key names we defined earlier to reduce the risk of mistyping a key name. We pass the entire dictionary to the standard NSUserDefaults instance’s registerDefaults() method. From that point on, NSUserDefaults will give us the values we specify here, as long as we haven’t set different values either in our app or in the Settings app.

This class is complete. You should be able to compile and run your application. It will look something like Figure 12-6, except yours will be showing whatever values you entered in your Settings application, of course. Couldn’t be much easier, could it?

Keeping It Real

Now you should be able to run your app, view the settings, and then press the Home button and open the Settings app to tweak some values. Hit the Home button again, launch your app again, and you may be in for a surprise. When you go back to your app, you won’t see the settings change! They’ll remain as they are, showing the old values.

Here’s the deal: in iOS, hitting the Home button while an app is running doesn’t actually quit the app. Instead, the operating system suspends the app in the background, leaving it ready to be quickly fired up again. This is great for switching back and forth between applications, since the amount of time it takes to reawaken a suspended app is much shorter than what it takes to launch it from scratch. However, in our case, we need to do a little more work, so that when our app wakes up, it effectively gets a slap in the face, reloads the user preferences, and redisplays the values they contain.

You’ll learn more about background applications in Chapter 15, but we’ll give you a sneak peek at the basics of how to make your app notice that it has been brought back to life. To do this, we’re going to sign up each of our controller classes to receive a notification that is sent by the application when it wakes up from its state of suspended execution.

A notification is a lightweight mechanism that objects can use to communicate with each other. Any object can define one or more notifications that it will publish to the application’s notification center, which is a singleton object that exists only to pass these notifications between objects. Notifications are usually indications that some event occurred, and objects that publish notifications include a list of notifications in their documentation. The UIApplication class publishes a number of notifications (you can find them in the Xcode documentation viewer, toward the bottom of the UIApplication page). The purpose of most notifications is usually pretty obvious from their names, but the documentation contains further information if you’re unclear about a given notification’s purpose.

Our application needs to refresh its display when the application is about to come to the foreground, so we are interested in the notification called UIApplicationWillEnterForegroundNotification. We’ll modify the viewWillAppear() method of our view controllers to subscribe to that notification and tell the notification center to call another method when that notification happens. Add the code below to both FirstViewController.swift and SecondViewController.swift:

The method itself is quite simple. First, it gets a reference to the standard user defaults object and calls its synchronize() method, which forces the User Defaults system to save any unsaved changes and also reload any unmodified preferences from storage. In effect, we’re forcing it to reread the stored preferences so that we can pick up the changes that were made in the Settings app. Next, the applicationWillEnterForeground() method calls the refreshFields() method, which each class uses to update its display.

Now we need to make each of our controllers subscribe to the notification. Add the following code in bold to the viewWillAppear: method in both FirstViewController.swift and SecondViewController.swift:

We start by getting a reference to our application instance, and then use that to subscribe to the UIApplicationWillEnterForegroundNotification, using the default NSNotificationCenter instance and a method called addObserver(_, selector:, name:, object:). We then pass the following to this method:

· For the observer, we pass self, which means that our controller class (each of them individually, since this code is going into both of them) is the object that needs to be notified.

· For selector, we pass a selector to the applicationWillEnterForeground() method we just wrote, telling the notification center to call that method when the notification is posted.

· The third parameter, UIApplicationWillEnterForegroundNotification, is the name of the notification that we’re interested in receiving.

· The final parameter, app, is the object from which we’re interested in getting the notification. We use a reference to our own application for this. If we passed nil for the final parameter instead, we would get notified any time any application posted theUIApplicationWillEnterForegroundNotification.

That takes care of updating the display, but we also need to consider what happens to the values that are put into the user defaults when the user manipulates the controls in our app. We need to make sure that they are saved to storage before control passes to another app. The easiest way to do that is to call synchronize as soon as the settings are changed, by adding one line to each of our new action methods in SecondViewController.swift:

Note Calling the synchronize() method is a potentially expensive operation because the entire contents of the user defaults in memory must be compared with what’s in storage. When you’re dealing with a whole lot of user defaults at once and want to make sure everything is in sync, it’s best to try to minimize calls to synchronize(), so that this whole comparison isn’t performed over and over again. However, calling it once in response to each user action, as we’re doing here, won’t cause any noticeable performance problems.

There’s one more thing to take care of to make this work as cleanly as possible. You already know that you must clean up your memory by setting properties to nil when they’re no longer in use, as well as performing other clean-up tasks. The notification system is another place where you need to clean up after yourself by telling the default NSNotificationCenter that you don’t want to listen to any more notifications. In our case, where we’ve registered each view controller to observe this notification in its viewWillAppear() method, we should unregister in the matching viewDidDisappear() method. So, in both FirstViewController.swift and SecondViewController.swift, add the following method:

Note that it’s possible to unregister for specific notifications using the removeObserver(_, name:,object:) method by passing in the same values that were used to register your observer in the first place. In any case, the preceding line is a handy way to make sure that the notification center forgets about our observer completely, no matter how many notifications it was registered for.

With that in place, it’s time to build and run the app and see what happens when you switch between your app and the Settings app. Changes you make in the Settings app should now be immediately reflected in your app when you switch back to it.

Switching to the Settings Application

To switch from the Bridge Control application to its settings, you need to go to the Home screen, launch the Settings application, find the Bridge Control entry, and select it. That’s a lot of steps. It’s so tiresome that many applications have opted to include their own settings screen rather than make the user go through all of that. Wouldn’t it be much nicer if you could just take the user directly to screen for your settings in the Settings application? Well, as of iOS 8, you can do just that. Remember the Open Settings Application button we added to SecondViewController inFigure 12-30? We wired it up to the settingButtonClicked() method in the view controller, but we didn’t put any code in that method. Let’s fix that now. Add the following code shown in bold:

This code uses a system-defined URL stored in the external constant UIApplicationOpenSettingsURLString (it’s value is actually app-settings:) to launch the Settings application right from our view controller. Run the application, switch to the second tab, and click theOpen Settings Application button—you’ll be taken directly to our settings screen, the one shown in Figure 12-3. That’s a great improvement. Unfortunately, though, there’s no quick way back—to return to our application, you have to go via the Home screen again. Maybe that’s something that will get fixed in a later iOS release.

Beam Me Up, Scotty

At this point, you should have a very solid grasp on both the Settings application and the User Defaults mechanism. You know how to add a settings bundle to your application and how to build a hierarchy of views for your application’s preferences. You also learned how to read and write preferences using NSUserDefaults, as well as how to let the user change preferences from within your application. You even got a chance to use a new project template in Xcode. There really shouldn’t be much in the way of application preferences that you are not equipped to handle now.

In the next chapter, we’re going to show you how to keep your application’s data around after your application quits. Ready? Let’s go!