Chapter 4. Developing the Flipside Controls

In This Chapter

  • Loading and saving user preferences

  • Adding a Constants.h file

  • Connecting user preferences to the Settings app

  • Adding a Settings bundle for user preferences

  • Adding outlets to the view controller

  • Connecting interface objects to your code in Interface Builder

As you recall from Chapter 1 of Book I, Apple considers applications that perform simple, highly defined tasks as utility apps. The Weather app, for example, deals only with the weather data. One of the characteristics of utility apps is that the user can change aspects of it — in Weather, for example, the user can add and change the cities that Weather shows the weather data for.

The DeepThoughts app is considered a utility, as it performs a simple, highly defined task. With DeepThoughts, the idea is to enable the user to enter his or her own words for the falling words animation (which I show you in Chapter 3 of this minibook), as well as change the speed of the animation. For these functions to work, you need to enable the app to save data entered by a user for the next time he or she fires up the app. In this chapter, I tackle how to get your app to save data entered by the user, and then you set up the Flipside View controls for users to enter the data.

Respecting User Preferences

Most people these days have spent enough time around computers that they know what I mean when I throw the term preferences around. On your desktop, for example, you can set preferences at the system level for things like security, screen savers, printing, and file sharing — just to name a few. You can also set preferences for applications. For example, you can set all sorts of preferences in Xcode — not to mention all those preferences in your browser and word-processing programs.

The latter are application-specific settings used to configure the behavior or appearance of an application. On the iPhone, application preferences are supported as well, but the iPhone displays all application-level preferences through the Settings app. (Its icon looks like a bunch of gears.) Whatever separate settings feature you come up with has to function within the framework of iPhone's Settings app; in effect, the Settings app makes you color within the lines.

What guidelines does the iPhone impose for preferences? Here's a short summary:

  • If you have preference values that are typically configured once and then rarely changed: Leave the task of setting preferences to the Settings app. On an iPhone, this would apply to things like enabling/disabling Wi-Fi access, setting wallpaper displays, setting up mail accounts, and any other preference you would set and then leave in place for a while.

  • If you have preference values that the user might want to change regularly: In this situation, you should consider having users set the options themselves in your app.

Tip

The iPhone's Weather app is a good example: Suppose you have this thing for Dubrovnik — where it happens to be 48° F as I'm writing this — and you'd like to add it to your list of preferred cities that you want the Weather app to keep tabs on. To load Dubrovnik into the Weather app, all you would have to do is tap the Info button at the bottom of the screen; the view will flip around, and you can add it to your list of cities. That's a lot easier than going back to the Home screen, launching Settings, adding the new city, and then launching the Weather app again.

With DeepThoughts, I could go either way — set up preferences using the Settings app (assuming they are rarely changed by users) or set up preferences from inside the app (assuming they are changed frequently by users). To make DeepThoughts a better example, I show you how to do it both ways.

Setting Up User Preferences

To save and read preferences, you use a built-in, easy-to-use class that lets you read and set user preferences from your app — NSUserDefaults. The class is also used by the Settings app (which has graciously consented to let us peons use it).

You use NSUserDefaults to read and store preference data to a default database, using a key value — just as you would access keyed data from an NSDictionary. (For more on key-value pairs in general and NSDictionary in particular, see Chapter 4 in Book II.) The difference here is that NSUserDefaults data is stored in the file system rather than in an object in memory — objects, after all, go away when the application terminates.

Tip

By the way, don't ask why the language experts put Defaults in the name rather than something to do with preferences — fewer letters, maybe — but that's the way it is. Just don't let their naming idiosyncrasies confuse you.

Storing the data in the file system rather than in memory gives you an easy way to store application-specific information. With the help of NSUserDefaults, you can easily store the state the user was in when he or she quit the application — or store something simple like a text string — which just so happens to be precisely what you need for DeepThoughts.

Identifying preferences for NSUserDefaults

The first thing you need to decide is where you plan on gathering and then using the user's preferences. In this example, the preferences are a) the text for the falling words, and b) the speed of the animation. The obvious place to use the preferences data is MainViewController.m, where it sets fallingWords and speed (refer to the listing for MainViewController.m in Chapter 3 of this minibook):

fallingWords = [[NSString alloc] initWithString:@"Peace Love
   Groovy Music"];
speed = 10.0;

And the obvious place to gather the preferences data is in the Flipside view, where the user enters the replacement text for fallingWords and changes the speed — and this would be in FlipsideViewController.m, as you see in this chapter. (Don't forget: I also show you how to offer these preference settings through the Settings app.)

It's really easy to both access and update a preference — as long as you have NSUserDefaults by your side. The trick here is to use the NSUserDefaults class to read and update the replacement text and speed. NSUserDefaults is implemented as a singleton, meaning there's only one instance of NSUserDefaults running in your application. To get access to that one instance, you invoke the class method standardUserDefaults:

[NSUserDefaults  standardUserDefaults]

standardUserDefaults returns the NSUserDefaults object. As soon as you have access to the standard user defaults, you can store data there and then get it back when you need it. To store data, you simply give it a key and tell it to save the data using that key.

The way you tell it to save something is by using the setObject;forKey: method. In case your knowledge of Objective-C is a little rusty (or not there at all), that's the way any message that has two arguments is referred to.

The first argument, setObject:, is the object you want NSUserDefaults to save. This object must be NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. In this case, savedData is an NSString, so you're in good shape.

The second argument is forKey:. In order to get the data back, and in order for NSUserDefaults to know where to save it, you have to be able to identify it to NSUserDefaults. You can, after all, have a number of preferences stored in the NSUserDefaults database, and the key tells NSUserDefaults which one you are interested in.

The particular keys you will use are kWordsOfWisdom for the falling words replacement text, kSpeed for the animation speed, and kMaxSpeed for the maximum speed possible. (Assuming you use a slider on the Flipside view to set the speed, the maximum speed is the far right point on the slider.)

Adding a Constants.h file

To use keys like kWordsOfWisdom, kSpeed, or kMaxSpeed, you need to define them in a Constants.h file. To implement the Constants.h file in your project, do the following:

  1. Select the project name (DeepThoughts) in the Groups & Files list and then choose File

    Adding a Constants.h file
  2. In the New File dialog that appears, choose Other from the listing on the left (under the Mac OS X heading) and then choose Empty File in the main pane, as shown in Figure 4-1.

  3. In the new dialog that appears, name the file Constants.h (as shown in Figure 4-2) and then click Finish.

    The new empty file is saved in your project, as shown in Figure 4-3.

Create an empty file.

Figure 4-1. Create an empty file.

Name the new file.

Figure 4-2. Name the new file.

The empty Constants.h file.

Figure 4-3. The empty Constants.h file.

With a new home for your constants all set up and waiting, all you have to do is add the constants you need, as shown in Figure 4-4:

#define kWordsOfWisdom  @"wordsOfWisdomPreference"
#define  kSpeed         @"speedPreference"
#define  kMaxSpeed      20.0

Having a Constants.h file in hand is great, but you have to let MainViewController.m know that you plan to use it, as I show in the next section.

Define the keys in the Constants.h file.

Figure 4-4. Define the keys in the Constants.h file.

Reading Preferences into the App

It may seem like starting at the end and working backwards, but it makes sense to show the code in DeepThoughts that uses these preferences first, and then show how you can enable the user to change and save these preferences in the Settings app, as well as from the Flipside view of the DeepThoughts app.

To put the preferences data to use in the app's view, you have to link it up with the view's controller — in this case, MainViewController. The best place to do that is viewDidLoad, which is invoked right after the view has been loaded from the nib file. viewDidLoad is found in the MainViewController.m file, so that's where you'd go to insert your code to use the preferences.

Edit the MainViewController.m file code where it sets fallingWords and speed. (Look in the first half of the code — refer to the listing for MainViewController.m in Chapter 3 of this minibook.) I've commented out the old code (which begins with //) and added the new code (shown in bold) in Listing 4-1.

Example 4-1. Reading the Settings into the App in MainViewController.m

#import "MainViewController.h"
#import "MainView.h"
#import "Constants.h"

@implementation MainViewController
- (id)initWithNibName:(NSString *)nibNameOrNil
   bundle:(NSBundle *)nibBundleOrNil {
    if (self = [super initWithNibName:nibNameOrNil
   bundle:nibBundleOrNil]) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad {

  [super viewDidLoad];
  self.view.backgroundColor = [UIColor  colorWithRed:.5
   green:3 blue:.7 alpha:1];
  [NSTimer scheduledTimerWithTimeInterval:.5 target:self
   selector:@selector(onTimer) userInfo:nil repeats:YES];
//  fallingWords = [[NSString alloc] initWithString:@"Peace
  Love Groovy Music"];
//  speed = 10.0;
  if (![[NSUserDefaults standardUserDefaults]
   objectForKey:kWordsOfWisdom]) {
    [[NSUserDefaults standardUserDefaults]setObject:@"Peace
   Love Groovy Music"
            forKey:kWordsOfWisdom];
    fallingWords = @"Peace Love Groovy Music";
  }
  else {
    fallingWords = [[NSUserDefaults standardUserDefaults]
                    stringForKey:kWordsOfWisdom];
  }

  if (![[NSUserDefaults standardUserDefaults]
   objectForKey:kSpeed] ){
    [[NSUserDefaults standardUserDefaults]setDouble:10.0
   forKey:kSpeed];
    speed = kMaxSpeed-10.0;}
  else {
        speed = kMaxSpeed-[[NSUserDefaults
   standardUserDefaults] doubleForKey:kSpeed] ;
 }
}
- (void)onTimer{

(The rest of the code in MainViewController.m stays the same; see Chapter 3 of this minibook for the full listing.)

Here's what this code does for you:

  1. Imports Constants.h into MainViewController.m.

    #import "Constants.h"

    You can now use these keys with NSUserDefaults to save and retrieve the user preferences.

  2. Checks to see whether the kWordsOfWisdom setting has been moved into NSUserDefaults.

    if (![[NSUserDefaults standardUserDefaults]
       objectForKey:kWordsOfWisdom]) {

    The Settings app or the Flipside view code moves the user's preferences into NSUserDefaults only after the application runs for the first time; Settings will, however, update preferences in NSUserDefaults if the user makes any changes.

  3. If the settings have not been moved into NSUserDefaults yet, the code uses the initial preference value ("Peace Love Groovy Music") for fallingWords.

    [[NSUserDefaults standardUserDefaults]setObject:@"Peace
       Love Groovy Music"
                forKey:kWordsOfWisdom];
            fallingWords = @"Peace Love Groovy Music";
  4. If the settings have been moved into NSUserDefaults, the code reads them in and then sets fallingWords to whatever the user's preference is.

    else {
        fallingWords = [[NSUserDefaults
       standardUserDefaults]
                        stringForKey:kWordsOfWisdom];
      }
  5. Repeats the procedures in Steps 2–4 with the speed setting.

    if (![[NSUserDefaults standardUserDefaults]
       objectForKey:kSpeed] ){
        [[NSUserDefaults standardUserDefaults]
       setDouble:10.0 forKey:kSpeed];
        speed = kMaxSpeed-10.0;}
      else {
            speed = kMaxSpeed-[[NSUserDefaults
       standardUserDefaults] doubleForKey:kSpeed] ;
      }

Your code is now ready to use the preference settings. Now, as I mentioned at the beginning of this chapter, one way to enable the user to change and save preferences is inside the Settings app — you set up the preferences in a Settings bundle that you add to your project. Another way to enable the user to change and save preferences is from inside your app (in this case, in the Flipside view). I'm going to tackle the Settings app first and deal with the Flipside view scenario later in this chapter.

Setting Up Preferences for the Settings App

The Settings app uses a property list, called Root.plist, found in the Settings bundle inside your application. The Settings app takes what you put in the property list and builds a Settings section for your application in its list of application settings as well as the views that display and enable the user to change those settings. The next sections spell out how to put that Settings section to work for your app.

Adding a Settings bundle to your project

For openers, you have to add a Settings bundle to your application. Here are the moves:

  1. In the Groups & Files list (at the left in the Xcode Project window), select DeepThoughts at the top and then choose File

    Adding a Settings bundle to your project

    The New File dialog appears.

  2. Choose Resource under the iPhone OS heading in the left pane and then select the Settings Bundle icon, as shown in Figure 4-5.

    Creating the Settings bundle.

    Figure 4-5. Creating the Settings bundle.

  3. Click the Next button.

    A new dialog appears, allowing you to change the name of the file.

  4. You don't have to change the default name of Settings.bundle, so just click Finish.

    You should now see a new item called Settings.bundle in the DeepThoughts project in the Groups & Files list.

  5. Click the triangle to expand the Settings.bundle subfolder in Groups & Files, as shown in Figure 4-6.

    You should see the Root.plist file as well as an en.lproj folder — the latter is used for dealing with localization issues, as discussed in Chapter 2 of Book V.

The Settings bundle appears in Groups & Files.

Figure 4-6. The Settings bundle appears in Groups & Files.

Setting up the property list

Property lists are widely used in iPhone apps because they provide an easy way to create structured data using named values for a number of object types.

Property lists all have a single root node — a dictionary, which means it stores items using a key-value pair, just as an NSDictionary does: All dictionary entries must have both a key and a value, as I describe in Chapter 4 of Book II. In this dictionary, there are two keys (refer to Figure 4-6):

  • StringsTable

  • PreferenceSpecifiers

The value for the first entry is a string — the name of a strings table used for localization, which I don't get into here. The second entry is an array of dictionaries — one dictionary for each preference. PreferenceSpecifiers is where you put the user preferences, which in this case are the text entered by the user for fallingWords and the animation speed selected on a slider by the user for speed. Here's how you'd get PreferenceSpecifiers to do that for you:

  1. In the Groups & Files list of the Project window, select the triangle next to the Settings.bundle file to reveal the Root.plist file, and then double-click the Root.plist file to open it in a separate window, as shown in Figure 4-7.

    Okay, you don't really have to do this, but you might find it easier to work with this file in its own window.

  2. In the Root.plist window you just opened (refer to Figure 4-7), click the triangle next to PreferenceSpecifiers, and click the triangle next to Item 0.

    PreferenceSpecifiers is an array designed to hold a set of dictionary nodes, each of which represents a single preference. For each item listed in the array, the first row under it has a key of Type; every property list node in the PreferenceSpecifiers array must have an entry with this key, which identifies what kind of entry this is. The Type value for Item 0 — PSGroupSpecifier — is used to indicate that a new group should be started. The value for this key actually acts like a section heading for a Table view.

  3. Double-click the value next to Title and delete the default Group (see Figure 4-7), and then type the title you want to appear in the Settings app.

    You will want to either delete the title's value of Group entirely or replace it with something like Your App's Settings. (I use Deep Thoughts Settings.)

    Item 0 in the Root.plist file is the title of your app's screen in the Settings app.

    Figure 4-7. Item 0 in the Root.plist file is the title of your app's screen in the Settings app.

  4. Click the triangle next to Item 1 to open it, as shown in Figure 4-8.

    The Type value for Item 1 is PSTextFieldSpecifier, which enables a text field that a user can type into. Its title is set to Phrase which is okay for your purposes.

  5. Double-click the value for Key in Item 1, and replace the default preference text with wordsOfWisdomPreference. (See Figure 4-8.)

    This is the user preference for the text for fallingwords, which the user can enter into a text field.

    Item 1 in the Root. plist file holds the phrase for the falling words.

    Figure 4-8. Item 1 in the Root. plist file holds the phrase for the falling words.

  6. Select Item 2 and choose Edit

    Item 1 in the Root. plist file holds the phrase for the falling words.

    I opened Item 2 in Figure 4-9 to show its contents. Item 2 is defined as a toggle switch, which you can modify by changing the Title value and so on. However, because you're not using it, you need to remove it from the file.

    Warning

    The item numbers change as you delete them, so you need to be careful. Fortunately, Undo is supported here; if you make a mistake, press

    Item 1 in the Root. plist file holds the phrase for the falling words.

    Item 3 is now Item 2, as shown in Figure 4-10.

  7. Select Item 2 and change the Key to speedPreference, the DefaultValue to 10, and the MaximumValue to 20 (and keep the MinimumValue set to 0).

    The Type value is PSSliderSpecifier, which enables a slider that a user can slide to set the value. The DefaultValue is the middle of the slider (10), with the MinimumValue at the far left and the MaximumValue at the far right.

    When you're done, the Root.plist window should look like Figure 4-10.

    Item 2 in the Root.plist file originally holds a toggle switch, which you don't need (so you can use Cut to remove it).

    Figure 4-9. Item 2 in the Root.plist file originally holds a toggle switch, which you don't need (so you can use Cut to remove it).

    Item 3 becomes Item 2 and now holds the speed slider preference.

    Figure 4-10. Item 3 becomes Item 2 and now holds the speed slider preference.

  8. Save the property file by pressing

    Item 3 becomes Item 2 and now holds the speed slider preference.

Testing your app with the Settings app

To see how your DeepThoughts app makes do with user preferences set within the Settings app, build and run your project as I describe in the last part of Chapter 3 of this minibook. When the iPhone Simulator appears and the app starts running, quit the app by clicking the Home button on the iPhone Simulator. Then drag to the right across the screen of the Simulator to go to the first Home screen, which contains the Settings app, as shown in Figure 4-11.

Run the Settings app in the iPhone Simulator.

Figure 4-11. Run the Settings app in the iPhone Simulator.

Click the Settings icon to launch the Settings app, and you can then click DeepThoughts in the menu of app settings, as shown in Figure 4-12.

The settings now appear in the DeepThoughts settings screen. Click the Phrase entry field (to simulate a user touching the field), and the onscreen keyboard appears for typing the replacement text for fallingWords, as shown in Figure 4-13. You can also click and drag the slider (to simulate a user touching and sliding the slider) to set the value for speed.

Choose Deep- Thoughts in the Settings app.

Figure 4-12. Choose Deep- Thoughts in the Settings app.

Change the entry in the Phrase field with the keyboard and slide the slider for the speed.

Figure 4-13. Change the entry in the Phrase field with the keyboard and slide the slider for the speed.

Click Settings in the top-left corner to return to the Settings menu, and then click the Home button to quit Settings. Drag across the Home screen to the left to switch to the next screen, which contains the DeepThoughts app. Click DeepThoughts to run it again, and the changes made in Settings should change the text and speed, as shown in Figure 4-14.

Run DeepThoughts to see the changes.

Figure 4-14. Run DeepThoughts to see the changes.

Warning

As you experiment with code and build and run your project, you need to delete the application and its data from the iPhone Simulator if you change anything of significance — before building and running again. The consequences of not doing so will become obvious when things don't work like you would expect them to. See Chapter 4 of Book I for details on deleting specific apps from the iPhone Simulator. For a fast reset of all apps and all data in the simulator, choose iPhone Simulator

Run DeepThoughts to see the changes.

Setting Up Preferences in the Flipside View

As you may recall from Chapter 2 of this minibook, FlipsideView-Controller owns the Flipside view's FlipsideView.xib (which you can open in the Interface Builder). You can add the interface objects you need for DeepThoughts — the text entry field for the replacement text for fallingWords, and the speed slider for speed — using Interface Builder, but first you have to put outlets in the code that connect your methods to the Interface Builder interface objects.

Note

The fact that a connection between an object and its outlets exists is actually stored in a nib file. When the nib file is loaded, each connection is reconstituted and reestablished, thus enabling you to send messages to the object. IBOutlet is the keyword that tags an instance-variable declaration so the Interface Builder application knows that a particular instance variable is an outlet — and can then enable the connection to it with Xcode.

In your code, it turns out that you need to create two outlets: one to point to the text entry field and one to point to the speed slider. To get this outlet business started, you need to declare each outlet, which you do with the help of the aforementioned IBOutlet keyword.

Add the bold lines of code in Listing 4-2 — the <UITextFieldDelegate> between UIViewController and the first curly brace, the code (including IBOutlet statements) after delegate; and before the last curly brace as well as all the @property statements before the @end.

Example 4-2. FlipsideViewController.h

@protocol FlipsideViewControllerDelegate;

@interface FlipsideViewController : UIViewController
   <UITextFieldDelegate> {

   id <FlipsideViewControllerDelegate> delegate;
           NSString*    wordsOfWisdom;
  IBOutlet UITextField *theTextField;
           float        sliderValue;
  IBOutlet UISlider    *slider;
}
 // @property (nonatomic, assign) id
    <FlipsideViewControllerDelegate> delegate;
- (IBAction)done;

@property (nonatomic, assign) id
   <FlipsideViewControllerDelegate> delegate;
@property (nonatomic, assign) NSString* wordsOfWisdom;
@property (nonatomic, assign) UISlider* slider;

@end

@protocol FlipsideViewControllerDelegate
- (void)flipsideViewControllerDidFinish:(FlipsideView-
   Controller *)controller;
@end

The IBOutlet statements declare the outlets, which will automatically be initialized with a pointer to the UITextField (theTextField) and the UISlider (slider) when the application is launched. But while this will happen automatically, it won't automatically happen automatically. You have to help it out a bit.

Objective-C properties

In procedural programming — you know, all that Linux Kernel stuff — variables are generally fair game for all. But in object-oriented programming, a class's instance variables are tucked away inside an object and shouldn't be accessed directly. The only way for them to be initialized is for you to create what are called accessor methods, which allow the specific instance variable of an object to be read and (if you want) updated. Creating accessor methods is a two-step process that begins with a @property declaration, which tells the compiler that there are accessor methods. And that is what you did in the preceding section; you coded corresponding @property declarations for each IBOutlet declaration.

The methods that provide access to the instance variables of an object are called accessor methods, and they effectively get (using a getter method) and set (using a setter method) the values for an instance variable. Although you can code those methods yourself, it can be rather tedious. This is where properties come in. The Objective-C Declared Properties feature provides a simple way to declare and implement an object's accessor methods. The compiler can synthesize accessor methods according to the way you told it to in the property declaration. Objective-C creates the getter and setter methods for you by using an @property declaration in the interface file, combined with the @synthesize declaration in the implementation file.

All that being said, at the end of the day, you need to do three things in your code to have the compiler create accessors for you:

  1. Declare an instance variable in the interface file.

  2. Add an @property declaration of that instance variable in the same interface file (usually with the nonatomic attribute).

    The declaration specifies the name and type of the property as well as some attributes that provide the compiler with information about how exactly you want the accessor methods to be implemented.

    For example, the declaration

    @property (nonatomic, assign) NSString* wordsOfWisdom;

    declares a property named wordsOfWisdom, which is a pointer to an NSString object. As for the two attributes — nonatomic and assignnonatomic tells the compiler to create an accessor to return the value directly, which is another way of saying that the accessors can be interrupted while in use. (This works fine for applications like this one.)

    The second value, assign, tells the compiler to create an accessor method that sends an assign message to any object that is assigned to this property.

  3. Use @synthesize in the implementation file so that Objective-C generates the accessors for you.

    The @property declaration only declares that there are accessors. It is the @synthesize statement that tells the compiler to create them for you. You add this statement in the next section, along with more code, to the FlipsideViewController.m implementation file. (Cue the next section!)

Adding methods for the interface objects

Okay, time to add the bold lines of code in Listing 4-3 to the FlipsideViewController.m implementation file.

Example 4-3. FlipsideViewController.m

#import "FlipsideViewController.h"
#import "MainViewController.h"
#import "Constants.h"

@implementation FlipsideViewController

@synthesize delegate , wordsOfWisdom, slider;

# pragma mark textField methods
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
    {
  [textField setReturnKeyType:UIReturnKeyNext];
  return YES;
}

- (BOOL)textFieldShouldReturn:(UITextField *)textField {
  [textField resignFirstResponder];
   return YES;

}

- (void)textFieldDidEndEditing:(UITextField *)textField {
  wordsOfWisdom = textField.text;

}
# pragma mark view methods
- (void)viewDidLoad {

  [super viewDidLoad];
  self.view.backgroundColor = [UIColor
   viewFlipsideBackgroundColor];
NSLog(@"Slider value %f parent value %f" ,slider.value ,
      ((MainViewController*)(self.parentViewController)).speed);
  slider.value = + kMaxSpeed - ((MainViewController*)(self.
      parentViewController)).speed; //added
  NSLog(@"Slider value %f parent value %f" ,slider.value ,
      ((MainViewController*)(self.parentViewController)).speed);
 }

 - (IBAction)done {
   if(! [theTextField.text isEqualToString: @"" ])
    wordsOfWisdom = theTextField.text; // added
    [self.delegate flipsideViewControllerDidFinish:self];
 }
 /*
  // Override to allow orientations other than the default
    portrait orientation.
  - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceO
    rientation)interfaceOrientation {
  // Return YES for supported orientations
  return (interfaceOrientation ==
    UIInterfaceOrientationPortrait);
  }
  */
 - (void)didReceiveMemoryWarning {
    // Releases the view if it doesn't have a superview.
     [super didReceiveMemoryWarning];

    // Release any cached data, images, etc that aren't in
    use.
 }

 - (void)viewDidUnload {
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
 }

 - (void)dealloc {
     [super dealloc];
 }
 @end

Although the @property declaration in the previous section tells the compiler that there are accessor methods, they still have to be created. Fortunately, Objective-C will create these accessor methods for you whenever you include an @synthesize statement for a property:

@synthesize delegate , wordsOfWisdom, slider;

The @synthesize statement tells the compiler to create accessor methods for you — one for each @property declaration.

Next, you add the text field methods to obtain the wordsOfWisdom:

# pragma mark textField methods
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
    {
  [textField setReturnKeyType:UIReturnKeyNext];
  return YES;
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
  [textField resignFirstResponder];
   return YES;
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
  wordsOfWisdom = textField.text;
}

The UITextFieldDelegate protocol defines the messages sent to a text field delegate as part of the sequence of editing its text. When the user performs an action that would normally start an editing session, the text field calls the textFieldShouldBeginEditing: method first to see whether editing should actually proceed. In most circumstances, you would simply return YES from this method to allow editing to proceed.

The text field calls the textFieldShouldReturn: method whenever the user taps the Return button on the keyboard to find out whether it should process the Return — you can use this method to implement any custom behavior when the Return button is tapped, but for your purposes, you simply return YES (which is the default), although you could return NO to ignore the Return button.

After saying "yes" to this and that, the real action happens with the textFieldDidEndEditing: method, which is called after the text field resigns its first responder status to tell the delegate that editing stopped for the specified text field, so that you now have the edited wordsOfWisdom.

Tip

The # pragma mark statement is simply a way to organize your methods in the Method list pop-up in the Xcode Text Editor Navigation bar (to the left of the Bookmarks menu). You use it with a label (such as textField methods or view methods) to add the label in bold to the Method list so that you can identify and keep separate the methods logically in the list.

Next, you modify the viewDidLoad method to obtain the speed from the slider (new code is in bold):

# pragma mark view methods
- (void)viewDidLoad {
[super viewDidLoad];
  self.view.backgroundColor = [UIColor
   viewFlipsideBackgroundColor];

  NSLog(@"Slider value %f parent value %f" ,slider.value ,
    ((MainViewController*)(self.parentViewController)).speed);
  slider.value = + kMaxSpeed - ((MainViewController*)(self.
    parentViewController)).speed; //added
  NSLog(@"Slider value %f parent value %f" ,slider.value ,
    ((MainViewController*)(self.parentViewController)).speed);
}
- (IBAction)done {
  if(! [theTextField.text isEqualToString: @"" ])
   wordsOfWisdom = theTextField.text; // added
  [self.delegate flipsideViewControllerDidFinish:self];
}

You wrap the code in NSLog statements to report back a message to the system log facility, but mostly this code assigns the slider.value to speed in MainViewController (which, as you recall from Chapter 3 of this minibook, uses speed to control the animated flowing text). Next, you deal with the possibility of a blank text field in the IBAction method. The code assigns the text field's text to wordsOfWisdom only if the field is not theTextField.text isEqualToString: @"".

Fine, but there are still a few problems: How will MainViewController know about the new speed assignment? How will the app save the preferences? You have to modify the MainViewController.h and MainViewController.m files by adding the code in bold, as I show in Listing 4-4 and Listing 4-5, to cover your bases here.

Example 4-4. MainViewController.h

#import "FlipsideViewController.h"

@interface MainViewController : UIViewController
   <FlipsideViewControllerDelegate> {

  UIImage    *fallingImage;
  NSString   *fallingWords;
  double      speed;
}

- (IBAction)showInfo;
@property (readwrite)  double speed;
@end

Example 4-5. MainViewController.m

#import "MainViewController.h"
#import "MainView.h"
#import "Constants.h"

@implementation MainViewController
@synthesize speed;

- (id)initWithNibName:(NSString *)nibNameOrNil
   bundle:(NSBundle *)nibBundleOrNil {
    if (self = [super initWithNibName:nibNameOrNil
    bundle:nibBundleOrNil]) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad {

  [super viewDidLoad];
  self.view.backgroundColor = [UIColor  colorWithRed:.5
   green:3 blue:.7 alpha:1];
  [NSTimer scheduledTimerWithTimeInterval:.5 target:self
   selector:@selector(onTimer) userInfo:nil repeats:YES];

//  fallingWords = [[NSString alloc] initWithString:@"Peace
   Love Groovy Music"];
//  speed = 10.0;
  if (![[NSUserDefaults standardUserDefaults]
   objectForKey:kWordsOfWisdom]) {
    [[NSUserDefaults standardUserDefaults]setObject:@"Peace
   Love Groovy Music"

   forKey:kWordsOfWisdom];
    fallingWords = @"Peace Love Groovy Music";
  }
  else {
    fallingWords = [[NSUserDefaults standardUserDefaults]
                    stringForKey:kWordsOfWisdom];
  }

  if (![[NSUserDefaults standardUserDefaults]
   objectForKey:kSpeed] ){
    [[NSUserDefaults standardUserDefaults]setDouble:10.0
   forKey:kSpeed];
    speed = kMaxSpeed-10.0;}
  else {
        speed = kMaxSpeed-[[NSUserDefaults
   standardUserDefaults] doubleForKey:kSpeed] ;
  }
}
- (void)onTimer{

  UILabel *fallingImageView = [[UILabel alloc]
   initWithFrame:CGRectMake(0, 0, 100, 30)];
  fallingImageView.text = fallingWords;
  fallingImageView.textColor = [UIColor  purpleColor];
  fallingImageView.font = [UIFont systemFontOfSize:30];
  fallingImageView.backgroundColor = [UIColor
   colorWithRed:.5 green:3 blue:.7 alpha:1];  // Transparent
   instead?
  fallingImageView.adjustsFontSizeToFitWidth = YES;

  int startX = round(random() % 80);
  int endX =  round(random() % 80);
  //speed of falling
  double randomSpeed = (1/round(random() % 100) +1) *speed;
  // image size;
  double scaleH = (1/round(random() % 100) +1) *30;
  double scaleW = (1/round(random() % 100) +1) *200;

  [self.view addSubview:fallingImageView];

  fallingImageView.frame = CGRectMake(startX, −100, scaleW,
   scaleH);

  fallingImageView.alpha = .75;

  [UIView beginAnimations:nil context:fallingImageView];
  [UIView setAnimationDuration:randomSpeed];
  [UIView setAnimationDelegate:self];
  [UIView setAnimationDidStopSelector:@selector(animationDone
   :finished:context:)];

  fallingImageView.frame = CGRectMake(endX, 500, scaleW,
   scaleH);

  [UIView commitAnimations];
}

-(void)animationDone:(NSString *)animationID
   finished:(NSNumber *)finished context:(id)context {
  UIImageView *fallingImageView = context;
  [fallingImageView removeFromSuperview];
  [fallingImageView release];
}

/*
 // Override to allow orientations other than the default
   portrait orientation.
 - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceO
   rientation)interfaceOrientation {
// Return YES for supported orientations
 return (interfaceOrientation ==
UIInterfaceOrientationPortrait);
 }
 */

- (void)flipsideViewControllerDidFinish:(FlipsideViewControl
   ler *)controller {

  if (controller.wordsOfWisdom ) {
    fallingWords = controller.wordsOfWisdom;
    [[NSUserDefaults standardUserDefaults]
   setObject:fallingWords  forKey:kWordsOfWisdom];
    [fallingWords retain];
  }

  if (controller.slider.value) {
    speed = kMaxSpeed-controller.slider.value; // can access
   it directly or use an ivar as above
    [[NSUserDefaults standardUserDefaults]setDouble:
   controller.slider.value forKey:kSpeed];
  }

    [self dismissModalViewControllerAnimated:YES];
 }
 - (IBAction)showInfo {

     FlipsideViewController *controller
      = [[FlipsideViewController alloc]
      initWithNibName:@"FlipsideView" bundle:nil];
     controller.delegate = self;
     controller.modalTransitionStyle =
      UIModalTransitionStyleFlipHorizontal;
     [self presentModalViewController:controller
      animated:YES];
     [controller release];
 }

(The rest of the code in MainViewController.m stays the same; see Chapter 3 of this minibook for the full listing.)

In Listing 4-4, you add the @property declaration for speed. Then in Listing 4-5, you add the @synthesize statement for the property.

Below the animation block in MainViewController.m (see Listing 4-5), you modify the flipsideViewControllerDidFinish: method to save the user preferences:

if (controller.wordsOfWisdom ) {
    fallingWords = controller.wordsOfWisdom;
    [[NSUserDefaults standardUserDefaults]
   setObject:fallingWords  forKey:kWordsOfWisdom];
[fallingWords retain];
  }

  if (controller.slider.value) {
    speed = kMaxSpeed-controller.slider.value; // can access
   it directly or use an ivar as above
    [[NSUserDefaults standardUserDefaults]setDouble:
   controller.slider.value forKey:kSpeed];
  }

   [self dismissModalViewControllerAnimated:YES];

As you'll recall from earlier in this chapter, you use standardUserDefaults (a NSUserDefaults class method) to gain access to the standard user defaults; you can store data there, and then get it back when you need it. To store data, you use the setObject;forKey: method. The first argument, setObject:, is the object you want NSUserDefaults to save (fallingWords); the second argument is forKey: (kWordsOfWisdom), which is how NSUserDefaults identifies it. For the slider value, setDouble: controller.slider.value forKey: sets the value of the specified default key to the double value.

The dismissModalViewControllerAnimated: method animates the view as it's dismissed. You're sending the message to the view controller that invoked the presentModalViewController method — in this case, the (IBAction)showInfo method. (Take another peek at Listing 4-5 at the end.)

Note

Don't forget to save your changes in Xcode; otherwise, Interface Builder won't be able to find the new code.

Connecting the Interface Objects in Interface Builder

You've created the outlets and their accessor methods in your code. Now you can create the connection in Interface Builder so that when the nib file is loaded, the nib loading code will create these connections automatically, using the accessors you had the compiler create for the slider and theTextField.

Now that you have the code ready to connect to the interface objects you want to use in the Flipside view, you can open the view in Interface Builder. Click Resources in the Groups & Files list and then double-click the FlipsideView.xib file to launch Interface Builder, as I describe in Chapter 2 of this minibook.

You can then click the Flipside View icon in the FlipsideView.xib window of Interface Builder, so that you can add the objects to it. If the Library window isn't already open, choose Tools

Connecting the Interface Objects in Interface Builder

Drag the slider, as shown in Figure 4-15, from the Library window over to the Flipside View window. Horizontal and vertical guides appear to help you place the slider where you want it.

Drag a slider to the Flipside view.

Figure 4-15. Drag a slider to the Flipside view.

You can select the horizontal slider in the Flipside View window and then drag its edges to make it longer. To set the slider's values, click the Attributes tab in the Inspector window (or choose Tools

Drag a slider to the Flipside view.

To connect the horizontal slider to your code, first click the triangle next to the Flipside View icon in the FlipsideView.xib window to reveal its contents (which includes Horizontal Slider) and then select Horizontal Slider. Then click the Connections tab in the Inspector window (or choose Tools

Drag a slider to the Flipside view.
Set the slider's Minimum, Maximum, and Initial values.

Figure 4-16. Set the slider's Minimum, Maximum, and Initial values.

Make a connection from the Horizontal Slider to the File's Owner.

Figure 4-17. Make a connection from the Horizontal Slider to the File's Owner.

Connect the Horizontal Slider to slider in your code.

Figure 4-18. Connect the Horizontal Slider to slider in your code.

Now, perform the same procedure with the Text Field object in the Interface Builder Library window. (If the Library window isn't already open, choose Tools

Connect the Horizontal Slider to slider in your code.
Drag a text entry field to the Flipside view.

Figure 4-19. Drag a text entry field to the Flipside view.

You can select your new text field in the Flipside View window and then drag its edges to make it longer, and change its attributes by clicking the Attributes tab in the Inspector window (or choosing Tools

Drag a text entry field to the Flipside view.

To connect the text field to your code, first select Round Style Text Field (which is now underneath Horizontal Slider) in the FlipsideView.xib window. Then click the Connections tab in the Inspector window (or choose Tools

Drag a text entry field to the Flipside view.

Finally, with Round Style Text Field still selected, drag from the connection point for delegate (at the top of the Text Field Connections window) to File's Owner, in order to hook up the text field's delegate connector to FlipsideViewController.

Make a connection from the Round Style Text Field to the File's Owner.

Figure 4-20. Make a connection from the Round Style Text Field to the File's Owner.

Connect the Round Style Text Field to the TextField in your code.

Figure 4-21. Connect the Round Style Text Field to the TextField in your code.

So it looks like you now have all the pieces in place for the DeepThoughts application — build and run your project to see what you have. You can now enter a new phrase, speed up or slow down the animation, and save both preferences using either the Flipside view or the Settings app.

Ah, but there's more you can do with this little app, as you see in the next chapter.

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

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