MOBILE

iphone Programming : Integrating Your Application - Application Preferences

9/26/2012 1:39:40 AM
Users look for application preferences in two main settings: in the application itself, and in the iPhone’s Settings application. For simple applications, applications with few preferences, and applications with preferences that need to be modified regularly, you should keep the preferences within the application itself. However, for more complicated applications, applications with complicated or numerous different preferences, and applications with preferences that the user will rarely have to modify, it’s preferable to use the Settings application.

Warning:

Despite it being done in some applications currently for sale on the App Store, Apple advises that you should never split your preferences between the Settings application and a custom settings screen inside your own application. According to Apple, “If you have preferences, pick one solution and use it exclusively.” This is good advice; having multiple places to change settings is confusing not just for the user, but also for you as a developer.


Adding a preferences panel for your application to the main Settings application is easy. You do this by adding a special Settings.bundle file to your application and then configuring the Root.plist file contained inside the bundle in the Xcode editor.

When the built-in Settings application launches, it checks each third-party application for the presence of a Settings Bundle. For each bundle it finds, it displays the application’s name and icon on the main page. When the user taps the row belonging to the application, Settings loads the Root.plist Settings Page file and uses that file to display your application’s main page of preferences.

Open the WhereAmI project in Xcode, right-click on the project’s icon in the Groups & Files pane in Xcode, and select AddNew File. In the pop-up window that appears, click on the Resource category in the lefthand pane underneath iPhone OS, select Settings Bundle, as shown in Figure 1, and click Next. Accept the default suggested name of Settings.bundle when prompted.

You’ll notice that the bundle appears in the Groups & Files pane in Xcode with an icon that looks a lot like a Lego brick. If you click on the arrow beside it to expand the bundle you’ll see the Root.plist file that contains an XML description of the settings root page, and an en.lproj directory containing the localized string resource file (for English). You can add further localizations to your Settings Bundle if needed.

Figure 1. Adding a Settings Bundle to your application


The default Settings Bundle contains some example settings. Click on the Build and Run button in the Xcode toolbar to compile and deploy the application into iPhone Simulator. Tap the simulator’s Home button to quit out of the application, and then find the Settings application on the Home screen. Tap the Settings application to open it, and you should see something similar to Figure 2.

Since we haven’t added an icon to the application , the space to the left of the WhereAmI entry is blank; if we had added an icon it would be displayed next to our application name. If you now tap the WhereAmI entry, you’ll be presented with the default preferences pane generated from the Settings Bundle, also shown in Figure 12-2.

Figure 2. The simulator Settings application (left) with the default Settings Bundle we added to the Where Am I? application (right)



Note:

If a file called Icon-Settings.png (a 29×29-pixel image) is located at the top level of your application’s bundle directory (drag it into the top level of your project under Groups & Files and check the box to copy the item), that icon is used to identify your application preferences in the Settings application. If no such image is present, the Settings application uses a scaled down version of your application’s icon file instead.


Returning to Xcode, click on the Root.plist file inside Settings.bundle to open it in the Xcode editor, and you’ll see the property list description of the Settings page. Option-click the disclosure triangle next to PreferencesSpecifiers, and you’ll see all the settings, as shown in Figure 3.

Figure 3. The Root.plist file in Settings.bundle



Note:

Like any property list file, Xcode by default displays the Root.plist file as a key-value pair list. However, you can see the raw XML of the Root.plist property list by right-clicking on the Root key and selecting Open AsSource Code File.


If you compare Figures Figure 2 and Figure 3, you can see how the property list file (Figure 3) compares to the rendered user interface (Figure 2):

  • Item 0 (PSGroupSpecifier) is a group label whose value is the string Group.

  • Item 1 (PSTextFieldSpecifier) is a text label whose value is the string Name.

  • Item 2 (PSToggleSwitchSpecifier) is a toggle switch labeled “Enabled” with a default value of YES.

  • Item 3 (PSSliderSpecifier) is a slider bar with a minimum value of 0, a maximum value of 1, and a default value of 0.5.

Each UI element is an item described in the PreferenceSpecifiers array.

To make this easier to work with, you can tell Xcode explicitly that the Root.plist file is an iPhone Settings bundle. From the Xcode menu select ViewProperty List TypeiPhone Settings plist (you may need to double-click on Root.plist before this option becomes available on the menu). This tells Xcode to format the contents of the property list a little differently, as shown in Figure 4, making it easier to understand and edit.

Figure 4. The Root.plist file formatted using the ViewProperty List TypeiPhone Settings plist from the Xcode menu bar


There are six possible property list keys:

  • Group (PSGroupSpecifier)

  • Title (PSTitleValueSpecifier)

  • Text Field (PSTextFieldSpecifier)

  • Toggle Switch (PSToggleSwitchSpecifier)

  • Multi Value (PSMultiValueSpecifier)

  • Slider (PSSliderSpecifier)

Additionally, although we won’t go into it here, you can point to child preference panes (additional settings pages) using the Child Pane (PSChildPaneSpecifier) property list key.

Let’s modify the default property key list provided by Xcode.

Click on Item 3 and press the Backspace key to delete it from the property list file; do the same for Item 1. You should be left with a Group and a Toggle Switch.

Rename the Group: under Item 0, double-click on the Title property’s value and enter Latitude & Longitude.

Keep the Toggle Switch unmodified. After doing this, the Root.plist file should resemble Figure 5.

Figure 5. The edited property list pane in the Xcode editor


Make sure you’ve saved your changes to the Root.plist file and click the Build and Run button in the Xcode toolbar. Once the application has started, tap the Home button and make your way to the Settings application. Tap the WhereAmI preference entry, and you should now see something closely resembling Figure 6. We’re going to use the preference pane to toggle whether we want the application to display the latitude and longitude on the screen when it displays our map.


Note:

When you run your application in iPhone Simulator, it stores preference values in ~/Library/Application Support/iPhone Simulator/User/Applications/<APP_ID>/Library/Preferences, where <APP_ID> is a randomly generated directory name. However, each time Xcode performs a clean install of your application, any previous version of the application’s preferences will be deleted.


Return to Xcode and click on the WhereAmIAppDelegate.m file to open it in the Xcode editor. Now add the following class method, which initializes the default settings for the application:

+ (void)initialize {
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSDictionary *defaultsToRegister =
      [NSDictionary dictionaryWithObject:@"YES"
                    forKey:@"enabled_preference"];
    [defaults registerDefaults:defaultsToRegister];
}

Figure 6. The edited property list pane in the Settings application


If your user has already accessed the application’s settings inside the iPhone Settings application, the default settings will already have been initialized. If this has not been done, the values will not exist and will be set to nil (or in the case of Booleans, to NO). As the application delegate is loaded, this method initializes the user defaults (the initialize: message is sent to each class before it receives any other messages).

Using this method to set the defaults has the unfortunate side effect that you have to specify your defaults in two places: in the Root.plist file, where they properly belong; and in your application delegate, where they don’t.

The right way to deal with this problem is to read in the defaults from the Settings.bundle file which is stored as part of your application. To do this, replace the initialize: method with the following:

+ (void)initialize {

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSString *settingsBundle =
      [[NSBundle mainBundle] pathForResource:@"Settings" ofType:@"bundle"];
    NSDictionary *settings =
      [NSDictionary dictionaryWithContentsOfFile:
         [settingsBundle stringByAppendingPathComponent:@"Root.plist"]];

    NSArray *preferences = [settings objectForKey:@"PreferenceSpecifiers"];
    NSMutableDictionary *defaultsToRegister =
      [[NSMutableDictionary alloc] initWithCapacity:[preferences count]];

    [defaults registerDefaults:defaultsToRegister];
}

If your application preferences don’t exist when your application is launched, you can therefore read the values directly from the Settings.bundle file rather than having to store the defaults in two places.

You can check that your preference bundle is working correctly by adding the following into the application delegate’s applicationDidFinishLaunching: method and checking the Console (select RunConsole from the Xcode menu bar). Add the lines shown in bold:

- (void)applicationDidFinishLaunching:(UIApplication *)application {
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
				BOOL enabled = [defaults boolForKey:@"enabled_preference"];
				NSLog(@"enabled = %d", enabled);

    self.locationManager = [[[CLLocationManager alloc] init] autorelease];

    ... other code not shown ...
}

We may have working preferences, but they don’t do anything yet. Let’s change that right now. Click on the WhereAmIViewController.h interface file to open it in the Xcode editor, and add the following outlets to the declaration (inside the curly braces of the @interface block):

IBOutlet UIButton *backgroundButton;
IBOutlet UILabel *latLabel;
IBOutlet UILabel *longLabel;

There is no need to make them properties.

Make sure you’ve saved all your changes (Option-⌘-S), and then double-click on the WhereAmIViewController.xib NIB file to open it in Interface Builder. Click on File’s Owner, and in the Connections tab of the Inspector window connect the backgroundButton outlet to the UIButton we used as a background for the labels, as shown in Figure 7; then connect the latLabel and longLabel outlets to the “Latitude” and “Longitude” UILabels, respectively.

Figure 7. Connecting the new outlets to File’s Owner


Save your changes to the NIB file and return to Xcode. Then click on the WhereAmIViewController.m implementation file to open it in the Xcode editor. Add the following viewWillAppear: method:

- (void)viewWillAppear:(BOOL)animated {

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    if ( [defaults boolForKey:@"enabled_preference"] ) {
        backgroundButton.hidden = NO;
        latLabel.text = @"Latitude";
        longLabel.text = @"Longitude";
    } else {
        backgroundButton.hidden = YES;
        latLabel.text = @"";
        longLabel.text = @"";
    }

    [super viewWillAppear:animated];
}

This method checks the application preferences to see if Latitude & Longitude are enabled. If they are, we set the text of the labels appropriately and make sure the button is visible. Correspondingly, if Latitude & Longitude are disabled, we hide the button and empty both strings.

Finally, we have to go back into the application delegate file and make a small modification to the locationManager:didUpdateToLocation:fromLocation: method. Here we have to stop the application from printing the current latitude and longitude to the screen if Latitude & Longitude are disabled via preferences. Add the lines shown in bold (wrapping the two existing assignments):

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
				if ( [defaults boolForKey:@"enabled_preference"] ) {
  viewController.latitude.text =
    [NSString stringWithFormat:@"%f", newLocation.coordinate.latitude];
  viewController.longitude.text =
    [NSString stringWithFormat:@"%f", newLocation.coordinate.longitude];
}

This brackets the lines that set the text of the UILabels with an if() block; we set the text of the labels only if Latitude & Longitude are enabled in the preferences.

We’re done here. Make sure all of your changes have been saved, and click the Build and Run button in the Xcode toolbar to compile and deploy your application into iPhone Simulator.

By default, the Latitude & Longitude display is enabled, so everything should appear as before. However, if you disable Latitude & Longitude in Settings, quit out of Settings, and relaunch the Where Am I? application, you’ll see that Latitude & Longitude has disappeared, as shown in Figure 8.

Figure 8. With Latitude & Longitude enabled in the preferences (left) and disabled (right)


1. Accessing Global Preferences

As well as your own application preferences, you can programmatically access the device’s global preferences from your own application:

NSString *path = [NSHomeDirectory()
  stringByAppendingPathComponent:
  @"Library/Preferences/.GlobalPreferences.plist"];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:path];
NSLog(@"Phone number: %@", [dict objectForKey:@"SBFormattedPhoneNumber"]);

While there are a number of entries, the two keys that are probably going to be of most interest to you as a developer are AppleLocale and SBFormattedPhoneNumber. These are the current localizations used by the device; as I am based in the United Kingdom my AppleLocale is en_GB, and my phone number is formatted to the current locale.

You can also retrieve the phone number directly from the global preferences:

NSString *phoneNumber = [[NSUserDefaults standardUserDefaults]
  objectForKey:@"SBFormattedPhoneNumber"];
NSLog(@"Phone number: %@", phoneNumber);

You should be aware that the phone number might not always be available unless the number has been set in the PhoneMy Number preference panel in the Settings application on the device. This is not guaranteed to be the case for all devices, as some carriers don’t set this automatically.
Other  
  •  BlackBerry Java Application Development : installing other JDE component packages over-the-air
  •  BlackBerry Java Application Development : installing the JDE plugin for Eclipse Full installer
  •  Programming the iPhone : Progressive Enhancement - Audio Support
  •  Programming the iPhone : Progressive Enhancement - Accelerometer Support, Rotation Support
  •  Emergency Phone : Spareone
  •  Ipad : Wireless Sync Using the MobileMe Service (part 4) - Using MobileMe After Setup, How to Cancel Your MobileMe Account
  •  Ipad : Wireless Sync Using the MobileMe Service (part 3) - Set Up Your iPad to Access Your MobileMe Account
  •  Ipad : Wireless Sync Using the MobileMe Service (part 2) - Set Up MobileMe on Your Windows PC
  •  Ipad : Wireless Sync of Your Google or Exchange Information (part 2) - Working With the Google or Exchange Contacts and Calendar on your iPad
  •  Ipad : Wireless Sync of Your Google or Exchange Information (part 1) - Set Up your iPad to Access Your Google or Exchange Account
  •  
    Most View
    17 Killer Mac Apps Under $20 (Part 1) : Smartday, Eisenpower
    Thunderstruck ASUS Brings Thunderbolt 2 To Your PC (Part 1)
    Educational Software Tools (Part 1)
    The Gionee Dream D1 - Surprisingly Amazing
    Apple Store Insider Guide (Part 1)
    Upgrading and Converting to Access 2010 : INSTALLING MULTIPLE VERSIONS OF ACCESS ON ONE PC, CHANGING FILE FORMATS
    Combine Multiple PDF Files In Preview
    The Contemporary APUs - AMD Trinity vs Intel Ivy Bridge (Part 9)
    The Hit List - Comprehensive Task Manager
    Nexus 7 Vs Kindle Fire
    Top 10
    Mitsubishi Hybrids – One Direction
    Race To The Clouds – Honda R&D’S ’91 NSX (Part 2)
    Race To The Clouds – Honda R&D’S ’91 NSX (Part 1)
    Volkswagen Plug-In Hybrid Up – Double Act
    Pre/Power Amplifier Marantz SA8005/PM8005 Review (Part 2)
    Pre/Power Amplifier Marantz SA8005/PM8005 Review (Part 1)
    Smart TV Finlux 50FME242B-T Review (Part 2)
    Smart TV Finlux 50FME242B-T Review (Part 1)
    The Best Money Can Buy: Motherboards (Part 2) - Asus Rampage IV Black Edition, Asus Crosshair V Formula-Z
    The Best Money Can Buy: Motherboards (Part 1) - ASRock X79 Extreme 11, Asus Intel Z97 ROG Bundle, Gigabyte Z97X-GAMING G1