Chapter 8. Data: actions, preferences, and files

 

This chapter covers

  • Accepting user input through controls
  • Allowing user choice through preferences
  • Accessing and creating files

 

In the preceding chapters, we offered a tutorial on the most important features of the SDK: we outlined Objective-C and iOS and explained Xcode, we examined view controllers of all types, and we looked at the standard event and action models for the iPhone and iPad. In the process, we tried to provide the strong foundation that you need to do any type of iOS programming. Armed with that knowledge, and with the extensive documentation available online (or as part of Xcode), you should be able to start programming right away.

But we also want to offer you some additional information about many of the SDK’s best features. In the coming chapters, we’ll touch on some of the major categories of SDK tools and show you how to use them.

We’ll expand on the sample programs a bit. Having completed the introduction to the SDK, we can take advantage of your knowledge of Objective-C to incorporate at least one in-depth example in each upcoming chapter; our intent is to show how different objects can work together to create a more complex Objective-C project. We can’t give you ready-to-submit App Store programs because of the breadth of what we’re covering here, but expect to see some code examples that are more than a page long and that typically include some off-topic elements.

This chapter will kick off our look at the SDK toolkit with a discussion of data, which will describe many of the ways you can deal with information generally (and text specifically). We’ve broken this into a couple of broad categories. First, we’ll look at the ways users can input data into your program, focusing on actions and preferences. Second, we’ll examine ways that you can store and retrieve internal data using files.

8.1. Accepting user actions

The simplest way to accept new data from a user is through UIControls, a topic that we covered in some depth in the latter half of chapter 6 and that we’re looking at again here for the sake of completeness. Table 8.1 includes some notes on the controls that you can use to accept user input.

Table 8.1. Various controls allow you to accept user input, most using simple interfaces.

Control

Summary

UIButton Offers simple functionality when the user clicks a button. See section 6.5 for an example.
UlPageControl A pure navigation object that allows users to move between multiple pages using a trio of dots.
UlPickerView Not a UIControl object, but allows the user to select from a number of items in a “slot machine” selection. It includes the subclass UIDatePicker.
UISearchBar Not a UIControl object, but offers similar functionality to a UITextField. It provides an interface that includes a single-line text input, a search button, a cancel button, and a bookmark button. See section 9.2.3 for an example.
UlSegmentedControl A horizontal bar containing several buttons. See section 18.1.3 for an example.
UlSlider A slider that allows users to input from a range of approximate values. See section 6.6.2 for an example.
UlSwitch An on-off button of the sort used in preferences. See section 8.2.1 for an example.
UITextField A single-line text input and probably the most common control for true user input. It requires some work to make the keyboard relinquish control. See section 6.6.1 for a complete discussion and an example.
UITextView Not a UIControl object, but does allow the user to enter longer bits of text. As with a text field, you must have it resignFirstResponder status to return control to the program when the user has finished typing. As shown in the iPhone Notes utility, this is typically done with a separate Done button at the top of the interface, because the Return key is used to input returns. See section 8.3.4 for an example.
UIToolBar Not a UIControl object. Instead, it’s a bar meant to hold a collection of UIBarButtonItems, each of which can be clicked to initiate an action. The bar is easy to configure and change. See section 11.4.2 for an example.

Clearly, these controls serve a variety of uses. Many exist for pure user interface purposes, which we covered pretty extensively in chapter 6. What’s of more interest here are the text-input controls (UISearchBar, UITextField, and UITextView) that you’re likely to use in conjunction with files and databases. We’ll look particularly at UISearchBar and UITextView, the two text inputs that we hadn’t previously given much attention to, over the course of this chapter.

Not included in this table are the integrated controller pickers that allow users to input data and make choices using complex prebuilt systems. We’ll discuss these pickers in later chapters.

Controls are central to any real-life program, so you’ll see them throughout the upcoming chapters. Because you’ll be seeing lots of examples of their use, we can now move on to the next method of user data input: preferences.

8.2. Maintaining user preferences

Preferences are the way an iPhone or iPad program maintains user choices, particularly from one session to another. They’re a way to not only accept user input but also save it. You can use your own programmatic interface to maintain these preferences, or you can use the Settings interface provided in the iOS SDK.

If your program includes preferences that may change frequently, or if it would be disruptive for a user to leave your program to set a preference, you can create a preferences page within your program. This type of program-centric preferences page is seen in the Stocks and Maps programs, each of which has settings that can be changed on the backside of the main utility.

Alternatively, if your program has preferences that don’t change that much, particularly if the defaults are usually okay, you should instead set them using the system’s settings. Typically, you use this option when your configuration controls are pretty standard, because this method is limited to a specific set of possible interactions. This type of device-centric setting can be seen in the iPod, Mail, Phone, Photos, and Safari applications, all of which have their settings available under the Settings icon on the device screen.

Of the two, the latter is the Apple-preferred way of doing things, but we’ll touch on both, starting with creating your own preferences page. You should feel free to use either method, based on the needs of your program; but you should most definitely not mix the two styles of preferences, because that’s likely to be confusing for your users.

8.2.1. Creating your own preferences

You’ll typically use this method of creating preferences when your application has more than basic data to store. For example, if one of your application settings is a user photo, you can’t store this type of information in the built-in system settings. You need a custom interface to allow the user to pick a photo from their library.

Whenever you’re writing apps, you should always do your best to match the look, feel, and methodology of Apple’s existing programs. Looking through built-in programs can offer lessons about when and how to use personal preferences on your own. Here’s what the personal preferences of those built-in programs can tell you:

  • They’re used infrequently.
  • When they do appear, they’re used in conjunction with a program that has only a single page of content (like Stocks) or one that has multiple identical pages of content (like Weather).
  • They appear on the backside of a flipside controller.
  • The preferences appear in a special list view that includes cells.

You can easily accommodate these standards when building your own programs. You’ll do so over the next few examples, with the goal being to create the simple preferences table shown in figure 8.1.

Figure 8.1. This preferences page was built from scratch on the backside of a flipside controller.

Drawing the Preferences Page

If you’re going to create a program that has built-in preferences, you should do so using the Utility Application template. As you’ve previously seen, this will give you access to a flipside controller, which will allow you to create your preferences on the backside of your application.

To create the special cartouched list used by preferences, you must create a table view controller with the special UITableViewGrouped style. You can do this by choosing the Grouped style for your table view in the Attributes Inspector or by using the initWithStyle: method in Xcode. The following code shows the latter method by creating the UITableViewController subclass (here called PreferencesController) inside the flipside controller’s viewDidLoad method:

- (void)viewDidLoad {
    PreferencesController *myTableView = [[PreferencesController alloc]
        initWithStyle:UITableViewStyleGrouped];
    [self.view addSubview:myTableView.view];
}

After you’ve done this, you can fill in your PreferencesController’s table view using the methods described in chapter 5. You’ll probably use the cells’ accessoryView property, because you’ll want to add switches and other objects to the preference listing. The following listing shows the most important methods required to create a simple preferences page with two switches.

Listing 8.1. Following the table view methods to fill out your preferences table

This example generally follows the table view methodology that you learned in chapter 5. You use an array to set up your table view. In addition to a title, these (mutable) dictionaries include additional info about the switch that goes into the table view, including what it should be set to and what action it should call. This example shows one nuance we mentioned before: only NSObjects can be placed in an NSDictionary, so you have to encode a Boolean value in order to use it.

The initWithStyle: method must do two other things. First, it must create a mutable array to hold all your switches for later access. You do all the creation based on settingsList (or on whatever other means you used to pull in preferences data), because if you wait until you get to the table view methods, you can’t guarantee the order in which they’ll be created. If you didn’t fill the switch list here, you could get an out-of-bounds error—if, for example, the switch in row 1 was created before the switch in row 0. Note also that these switches are created with no particular location on the screen, because you’ll place them later. Second, the method must move your table down a bit to account for the navigation bar at the top of the flipside page .

The methods that define the section count, the section head, and the row count are all pretty standard. It’s the method that defines the contents of the rows that’s of interest, primarily because it contains code that takes advantage of the accessory-View property that we touched on in chapter 5. In this method, you read back the appropriate switch from your array and input it .

There’s no real functionality in this preferences page—that ultimately will depend on the needs of your program. But this skeleton should give you everything you need to get started. Afterward, you’ll need to build your methods (here, setMusic: and setSounds:), which should access the switchList array, and then do the appropriate thing for your program when the switches are toggled.

Switches are the most common element of a preferences page. The other common feature that you should consider programming is the select list. That’s usually done by creating a subpage with a table view all its own. It should be set in UITableView-Grouped style, like this table. You’ll probably allow users to checkmark one or more elements in the list.

Saving User Preferences

We’re leaving one element out of this discussion: what to do with your users’ preferences after they’ve set them. It’s possible that you’ll want to save user preferences only for the length of a single session, but it’s our experience that it can be confusing and even annoying to users. More commonly, you should save preferences from one session to another. We offer three different ways to do so:

  • Save the preferences in a file—Section 8.3 talks about file access. You can either save the preferences in plain text or else use a more regulated format like XML, which is covered in chapter 14.
  • Save the preferences in a database—Sections 9.1 and 9.3 cover this.
  • Save the preferences using NSUserDefaults—This option is discussed next.

NSUserDefaults is a storage mechanism that’s specific to user preferences, so we’ll cover it here.

Generally, NSUserDefaults is a persistent shared object that you can use to remember a user’s preferences from one session to another. It’s sort of like a preferences associative array. It has three major methods, listed in table 8.2.

Table 8.2. Notable methods for NSUserDefaults

Method

Summary

standardUserDefaults: Class method that creates a shared defaults object.
objectForKey: Instance method that returns an object for the key; numerous variants return specific types of objects such as strings, Booleans, and the like.
setObjectForKey: Instance method that sets a key to the object; numerous variants set specific types of objects such as strings, Booleans, and so on.

It would be simple enough to modify the previous preferences example to use NSUserDefaults. First, you’d change the init method to create a shared defaults object and then read from it when creating the settingListing array, as shown in the following listing.

Listing 8.2. Preferences setup with NSUserDefaults

The lines in which the prefValues are set are the new material here. The information is extracted from NSUSerDefaults first.

The methods called when each of these switches are moved can set and save changes to the default values. You’ll want to do other things here too, but the abbreviated form of these methods is shown in the following listing.

Listing 8.3. Setting and saving NSUserDefaults
-(void)setMusic:(id)sender {
    NSUserDefaults *myDefaults = [NSUserDefaults standardUserDefaults];
    UISwitch *musicSwitch = [switchList objectAtIndex:1];
    [myDefaults setBool:musicSwitch.on forKey:@"musicValue"];
}
-(void)setSounds:(id)sender {
    NSUserDefaults *myDefaults = [NSUserDefaults standardUserDefaults];
    UISwitch *soundsSwitch = [switchList objectAtIndex:0];
    [myDefaults setBool:soundsSwitch.on forKey:@"soundsValue"];
}

This functionality is simple. You call up NSUserDefaults, set any values you want to change, and then save them. If you call up your program again, you’ll find that the two switches remain in the position where you set them the last time you ran the program.

After you decide how to save your personal preferences, you’ll have a skeleton for creating your own preferences page; if that’s appropriate for your program, you’re finished. But that’s just one of two ways to let users add preference data to your program. More commonly, you’ll export your settings to the main Settings program. So, how do you do that?

8.2.2. Using the system settings

When you created a personal preferences page in the previous section, you used all the iOS programming skills you’ve been learning to date, creating objects and manipulating them. Conversely, using the system settings is much easier: it just requires creating some files.

 

About bundles

Xcode allows you to tie multiple files together into a coherent whole called a bundle. In practice, a bundle is just a directory. Often a bundle is made opaque, so that users can’t casually see its contents; in this case, it’s called a package.

The main advantage of a bundle is that it can invisibly store multiple variants of a file, using the right one when the circumstances are appropriate. For example, an application bundle can include executable files for different chip architectures or in different formats.

When working with Xcode, you’re likely to encounter three different types of bundles: framework bundles, application bundles, and settings bundles. All frameworks appear packaged as framework bundles, although that’s largely invisible to you. An application bundle is what’s created when you compile a program to run on your iPhone or iPad; we’ll talk about how to access individual files in a bundle in the next section, when we talk about files in general. Finally, the settings bundle contains a variety of information about system settings, a topic that we’ll address now.

You can find more information about how to access bundles in the NSBundle and CFBundle classes.

 

To begin using the system settings, you must create a settings bundle. You do this in Xcode by choosing the File > New File option. To date, you’ve only created new files using the Cocoa Touch Classes option (starting in section 3.3). Now, you should instead choose Resources in the side pane, which gives you the option to create one sort of settings file: Settings Bundle. When you do this, Settings.bundle is added to your current project.

Editing Existing Settings

Root.plist is an XML property list file, but as usual, you can view it in Xcode, where it appears as a list of keys and values. All of your settings appear under the Preference-Specifiers category, as shown in figure 8.2.

Figure 8.2. This look at system settings reveals some of Root.plist’s PreferenceSpecifiers.

You can enter seven types of data in the Settings plist file, each of which creates a specific tool on the Settings page. Of these, four appear by default in the plist file at the time of this writing and are the easiest to modify. All seven options are shown in table 8.3.

Table 8.3. Different preference types let you create different tools on the Settings page.

Preference

Summary

Default

PSChildPaneSpecifier Points to a subpage of preferences  
PSGroupSpecifier Contains a group header for the current table section
PSMultiValueSpecifier Points to a subpage containing a select list  
PSSliderSpecifier A UISlider
PSTextFieldSpecifier A UITextField
PSTitleValueSpecifier Shows the current, unchangeable value of the preference  
PSToggleSwitchSpecifier A UISwitch

The plist editor is simple to use and lets you easily do the vast majority of work required to create the settings for your program. You can cut and paste the existing four preferences (noted by checkmarks in table 8.3) to reorder them or create new instances of the four existing preference types. Then, you fill in their data to create preferences that look exactly like you want them.

For any setting, the Type string always describes which sort of preference you’re setting. Other settings define what you can change. For example, to change the text that appears in a PSGroupSpecifier, you adjust the Title string inside the PSGroup-Specifier dictionary. Changing the PSSliderSpecifier, PSTextFieldSpecifier, and PSToggleSwitchSpecifier is equally easy. The only thing to note on those is the Key string, which sets the name of the variable for the preference. You’ll need that name when you want to look it up from inside your program (a topic we’ll return to).

Creating New Settings

The remaining three preferences are a bit harder to implement because you don’t have a preexisting template for them sitting in the default Root.plist file. But all you have to do is create a dictionary that contains the right values.

When you click individual rows in the plist editor, you’ll see some iconic options to help you create new preferences. At any time, you can create new Preference-Specifiers (which is to say, new preferences) by clicking the plus (+) symbol to the right of the current row. You can likewise add to dictionaries or arrays by opening them and then clicking the indented row symbol to the right of the current row.

A PSTitleValueSpecifier is an unchangeable preference. It shows the preference name and a word on the Settings page. Its dictionary includes a Type (string) of PSTitleValueSpecifier, a Title (string) that defines the name of the preference, a Key (string) that defines the variable name, and a DefaultValue (string).

A PSMultiValueSpecifier is a select list that appears on a subpage. Its dictionary contains a Type (string) of PSMultiValueSpecifier, a Title (string), a Key (string), a DefaultValue (string), a Titles (array) that contains a number of String items, and a matched Values (array) that contains Number items.

Figure 8.3 shows what these two items look like, laid out in Xcode.

Figure 8.3. This display shows how a PSTitleValueSpecifier and a PSMultiValueSpecifier look in Xcode.

The last sort of setting, PSChildPaneSpecifier, does something totally different: it lets you create additional pages of preferences.

Creating Hierarchical Settings

If necessary, you can have multiple pages of settings. To create a subpage, use the PSChildPaneSpecifier type. It should contain a Type (string) of PSChildPane-Specifier, a Title (string), and a File (string) that contains the new plist file without an extension.

After you’ve done this, you need to create your new plist file. There is currently no easy “Add plist” option, so we suggest copying your existing Root.plist file, renaming it, and going from there.

We’ve put together an example of all seven preference types in figure 8.4. It shows the types of preference files that you can create using Apple’s built-in functionality.

Figure 8.4. As seen on an iPhone, in order from top to bottom, a Group, a TextField, another Group, a Switch, a TitleValue, a MultiValue, a ChildPane, a third Group, and a Slider

Now you know everything that’s required to give your users a long list of preferences that they can set. But how do you use them from within Xcode?

Accessing Settings

Settings end up encoded as variables. As you saw when looking through the plist editor, each individual preference is an NSString, an NSArray, an NSNumber, or a Boolean. You can access these variables using the shared NSUserDefaults object. We already discussed this class in the last section; it so happens that Apple’s settings bundle uses it, as we suggested you might. The functionality remains the same. You can create it as follows:

[NSUserDefaults standardUserDefaults];

When you’ve done that, you can use NSUserDefaultsobjectForKey: methods, such as arrayForKey:, integerForKey:, and stringForKey:, as appropriate to access the information from the settings. For example, the following code applies a string from the settings to a label:

myLabel.text = [[NSUserDefaults standardUserDefaults]
    stringForKey:@"name_preference"];

Similarly, you can save new settings by using the various setObjectForKey: methods—although we don’t think this is a particularly good idea if users are otherwise modifying these values in Settings.

There is one considerable gotcha that you must watch for: if a user hasn’t yet accessed the settings for your program, then all settings without default values have a value of nil. This means you either need to create your preferences by hand or build defaults into your program, as appropriate.

Most of the time, you’ll only need to retrieve the setting values, as described here; but if more is required, you should look at the class reference for NSUserDefaults.

That concludes our look at the two ways to create preferences for your programs and also at how users can input data into your program. But user input represents just one part of the data puzzle. Certainly, a lot of important data comes from users, but data can also come from various files and databases built into your program or into the device. Retrieving data from those sources is the topic of the latter half of this chapter.

8.3. Opening files

When we talked about bundles earlier in this chapter, you saw how the iPhone and iPad arrange their internal information for programs. That arrangement becomes vitally important when you’re trying to access files that you’ve added to a project.

Fortunately, for the iPhone, you can look at how your program’s files are arranged when you’re testing applications on the Simulator. Each time you run a program, the program is compiled to a directory under ~/Library/Application Support/iPhone Simulator/Users/Applications. The specific directory has a hexadecimal name, but you can search to find the right one. Figure 8.5 shows an example of the directory for the sample program that we used to set up the system preferences example (the subdirectories are the same for any basic program). The process is similar for the iPad.

Figure 8.5. Compiled programs contain several directories full of files.

As shown, there are four directories of files for this one simple program. The majority of the content appears in the application bundle, which in this example is called systempreferences.app. There, you find everything you’ve added to your project, including text files, pictures, and databases. The other three directories you can use are Documents, Library, and tmp.

These are all intended to be used for files that are created or modified when the program is run. Documents should contain user-created information (including new or modified text files, pictures, and databases), Library should contain more programmatic items (like preferences), and tmp should contain temporary information. Each of these directories starts out empty, other than the fact that Library maintains a local copy of your system settings. We’ll talk about how and why you fill them momentarily. First, let’s look at how to access your bundle; later, we’ll discuss how to access other directories and also how to manipulate files. At the end of the section, we’ll put everything together with a concrete example.

8.3.1. Accessing your bundle

In previous chapters, we’ve shown how easy it is to add files to your project. You drag the file into Xcode, and everything is correctly set up so that the file will become part of your program when it compiles. As you now know, that means the file is copied into your application bundle.

For many bundled files, you don’t have to worry about anything beyond that. For example, when you work with picture files, you enter the name of the file in Xcode, and the SDK automatically finds it for you. But if you want to access a file that doesn’t have this built-in link, you need to do a bit more work.

Whenever you’re working with the filesystem on the iPhone or iPad, access is abstracted through objects. You send messages that tell the SDK what area of the file-system you’re looking for, and the SDK then gives you precise directory paths. The benefit of this abstraction is that Apple can reorganize the filesystem in future releases, and your program won’t be affected at all.

The first files you’ll want to access will probably be in your bundle: files that you included when you compiled your program. Accessing a bundle file is usually a two-step process, as shown in this database example (which we’ll return to in the next section):

NSString *paths = [[NSBundle mainBundle] resourcePath];
NSString *bundlePath = [paths stringByAppendingPathComponent:dbFile];

In this example, mainBundle returns the directory path that corresponds to your application’s bundle, and resourcePath expands that to be the directory path for the resources of your program (including, in this case, a database, but this could be anything else you added to your program). Finally, you use stringByAppendingPath-Component: to add your specific file to the path. This NSString method makes sure a path is constructed using slashes (//) as needed.

The result is a complete path that can be handed to other objects as needed. You’ll see how that works with a database in the next section. You can likewise use it for UImage’s imageWithContentsOfFile: method or NSFileHandle’s fileHandleForReadingAtPath method. We’ll return to the latter shortly.

But there’s one fundamental problem with accessing files in the application bundle: you can’t modify them. Apple generally suggests that you should treat the application bundle as read only, and there’s a real penalty if you don’t: your program will stop working because it won’t checksum correctly. This means that the application bundle is great for files that don’t change, but if you want to modify something (or create something new), you need to use the other directories we mentioned, starting with the Documents folder.

8.3.2. Accessing other directories

When you’re working with directories other than the bundle, you have to think about two things: how to access those files and how to move files among multiple directories.

Retrieving a File

When a file is sitting in your Documents directory, you can retrieve it much as you retrieved files from the bundle directory:

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
    NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *docPath = [documentsDirectory
    stringByAppendingPathComponent:dbFile];

The magic here occurs in the NSSearchPathForDirectoriesInDomains function. The first argument is usually NSDocumentDirectory or NSLibraryDirectory, depending on which directory you want to get to. The other two arguments should always be the same for the iPhone and iPad. The result is an array of strings, each containing a path. The first path in the NSArray is usually the right one, as shown here. You can then use the stringByAppendingPathComponent: method, as before, to build the complete path for your file. Voila! You’ve now used some slightly different methods to access a file in your Documents directory rather than the bundle directory.

Copying a File

There’s been a slight disconnect in our discussion of files and directories to date. When you compile your project, all of your files are placed into your application bundle. But if you ever want to edit a file, it must be placed in a different directory, such as Documents. So how do you get a file from one place to the other? You use the NSFileManager:

NSFileManager *fileManager = [NSFileManager defaultManager];
success = [fileManager copyItemAtPath:bundlePath toPath:docPath
    error:$rror];

The file manager is a class that allows you to easily manipulate files by creating them, moving them, deleting them, and otherwise modifying them. As is the case with many classes you’ve seen, you initialize it by accessing a shared object. You can do lots of things with the file manager, including copying (as you’ve done here) and checking for a file’s existence (which we’ll demonstrate shortly). You should look at the NSFileManager class reference for complete information.

As you’ll see, the NSFileManager is one of numerous classes that you can use to work with files.

8.3.3. Manipulating files

It’s possible that after you’ve built your file path, you’ll be ready to immediately read the file’s contents, using something like the UIImage methods (which we’ll touch on in chapter 13) or the functions related to SQLite (which we’ll cover in the next chapter). But it’s also possible that you’ll want to manipulate the raw files, reading and parsing them in your code, as soon as you’ve created a file path. There are numerous ways to do this, as shown in table 8.4.

Table 8.4. Ways to manipulate files using the SDK

Class

Method

Summary

NSFileHandle fileHandleForReadingAtPath:
fileHandleForWritingAtPath:
fileHandleForUpdatingAtPath:
Class methods that allow you to open a file
NSFileHandle readDataOfLength: Returns an NSData containing the specified number of bytes from the file
NSFileHandle readDataToEndOfFile: Returns an NSData with the rest of the file’s content
NSFileHandle closeFile: Closes an NSHandle
NSFileManager contentsAtPath: Returns an NSData with the complete file’s contents
NSData initWithContentsOfFile: Creates an NSData with the complete file’s contents
NSData writeToFile:atomically: Writes the NSData to a file
NSString stringWithContentsOfFile:encoding:error: Class method that returns an NSString with the complete file’s contents
NSString initWithData:encoding: Returns an NSString with the NSData’s contents
NSString writeToFile:atomically:encoding:error: Writes the NSString to a file

As table 8.4 shows, you can access files in a huge variety of ways after you’ve created a file path. If you’re a C programmer, opening a file handle, reading from that file handle, and finally closing that file handle is apt to be the most familiar approach. Or, you can use a shortcut and go straight to the NSFileManager and have it do the whole process. Even quicker is using methods from NSData or NSString to directly create an object of the appropriate type.

Any of these simpler methods will cost you the ability to step through a file byte by byte, which may be a limitation or a benefit, depending on your program. But with the simpler methods, you need only a single line of code:

NSString *myContents = [NSString stringWithContentsOfFile:myFile
    encoding:NSASCIIStringEncoding error:&error];

Table 8.4 also lists a few ways to write back to files, including simple ways to dump an NSData object or an NSString object to a file. There are also other ways. When you decide which set of methods you’re most comfortable using, you should consult the appropriate class reference for additional details.

When you’re working with files, you’re likely to be doing one of two things. Either you have files that contain large blobs of user information, or you have files that contain short snippets of data that you’ve saved for your program. To demonstrate how to use a few of the file objects and methods, you’ll tackle the first problem by building a simple notepad prototype.

 

File content

In this section—and in our next example—we’re largely assuming that files contain plain, unstructured text. But this doesn’t have to be the case. XML is a great way to store local data in a more structured format. Chapter 14 covers how to read XML and includes an example of reading local XML data.

 

8.3.4. Filesaver: a UITextView example

This program lets you maintain a text view full of information from one session to another. It’s relatively basic, but you can imagine how you could expand it to mimic the Notepad program, with its multiple notes, toolbars, navigator, and image background.

The following listing shows this simple filesaver example. The objects, as usual, were created in Xcode: a UIToolBar (with associated UIBarButtonItem) and a UITextView.

Listing 8.4. A prototype notepad program that maintains a text field as a file

This program shows how easy it is to access files. The hardest part is determining the path for the file, but that involves using the path-creation methods we looked at a few sections back. When you have your path, you save it as a variable so that you won’t have to re-create the path later . Next, you use NSFileManager to determine whether a file exists. If it does, you can immediately fill your UITextField with its content. Finally, you set a keyboardIsActive variable, which you update throughout the program.

As we’ve previously noted, the objects that pull up keyboards are a bit tricky, because you have to explicitly get rid of the keyboard when editing is done. For UITextFields, you can turn the Return key into a Done key to dismiss the keyboard; but for a UITextView, you usually want the user to be able to enter returns, so you must typically create a bar at the top of the page with a Done button. Figure 8.6 shows this layout of items.

Figure 8.6. The filesaver application with the keyboard activated on both the iPhone and the iPad

When the user presses Done, the finishEditing: method is called, which resigns the first responder, making the keyboard disappear (unless you’re not editing, in which case it closes the program).

The last two methods are defined in the UITextFieldDelegate protocol. When editing begins on the text field, the program checks to see if the starting text is still there, and if so clears it. When editing ends on the text field, the content is saved to your file. Finally, the keyboardIsActive variable is toggled, to control what the Done button does in each state.

As you saw in table 8.4, there are numerous other ways to read files and save them. The methods in listing 8.4 are simple, but they allow you to make good use of your notepad’s file.

Files are okay to use for saving one-off data, but if you’re storing a lot of really large data, we suggest using a database when it’s available. And on the iPhone and iPad, a database is always available, as you’ll see in chapter 9.

8.4. Summary

In this chapter, we covered a variety of ways that you can import primarily text-based data into your program. User action is one of the most important methods, one well covered by previous sections. In addition to UITextFields, UITextViews, and UISearchBars, many nontextual interface options are available.

Preferences mark the other major way users can influence your program. You can either program them manually or use the System Setting bundle.

Ultimately, user input is somewhat limited on the iPhone because of the slow typing speed. If you’re dealing with piles of text, you’ll more frequently want to pull that data from an existing resource. The iPad doesn’t suffer from this issue, because users can type more quickly on the keyboard.

Files are the traditional way to access large amounts of data. We’ll return to files when we deal with photos and sounds in the later chapters. Databases are frequently an easier way to access data, particularly if the data is well organized, as you’ll see in chapter 9.

There’s only one data-input method that we’ve largely ignored: the internet. We consider it so important that we’ll cover it in chapter 14.

The data-input and -retrieval methods discussed in this chapter and the next will form a foundation for much of the work you do with the iPhone and iPad, because ultimately everything is data. You’ll need to retrieve data when you work with images and sounds. Similarly, you may want to save data from your accelerometer, from your Core Location, or when you create a graphic. Keep what you’ve learned here in your back pocket as you move on to the rest of the iOS toolbox.

We’re now ready to discuss more advanced data access techniques, including interfacing with the Address Book and saving persistent data with SQLite.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.206.254