Publishers of technology books, eBooks, and videos for creative people

Home > Articles

Creating iOS 5 Apps: Developing Views and View Controllers

In this chapter from his book, Rich Warren continues to use his Health Beat sample application to show you how to work with Views and View Controllers.
This chapter is from the book

In this chapter, we will flesh out the enter weight and history views for our Health Beat application. As we proceed, we will gain more experience graphically laying out views and linking user interface elements to their view controllers. We will also learn how to populate, monitor, and control a table view; how to set up static table views; and the differences between using a navigation controller and simply presenting modal views.

This chapter also includes a discussion of advanced techniques. We use Core Animation to modify a view’s appearance, which lets us create rounded buttons with custom background colors.

Entering Weight Data

Let’s start by modifying our enter weight view. Open the MainStoryboard.storyboard file, and zoom in on the enter weight scene.

Drag a label from the library and drop it anywhere in the view. Double-click the label, and change the text to Enter Today’s Weight. In the Attributes inspector, change the font to System Bold 24.0. You can tap the fonticon.jpg icon to bring up the Fonts window. Next, choose Editor > Size to Fit Content from the menu bar. Finally, center the label along the top of the view using the guidelines (Figure 4.1).

Figure 4.1

Figure 4.1 Centering the label

Drag out a second label and position it below the first. Change its text to Current Date and Time. Don’t worry, that’s just a placeholder. We will replace this text at runtime. Of course, we don’t know exactly how long the date and time string will be, but we still want it centered under our title. The easiest way to do this is to set the label’s Alignment attribute to Centered, and then stretch the label so that it fills the view from margin to margin (Figure 4.2).

Figure 4.2

Figure 4.2 Stretching a label to fill the view

Next, place a text field under the Current Date label. Stretch it so that it also fills the view from margin to margin. Set the attributes as shown in Table 4.1.

Table 4.1 Text Field Attributes

Attribute Name

Value

Alignment

Centered

Capitalization

None

Correction

No

Keyboard

Numbers and Punctuation

Return Key

Done

Auto-enable Return Key

On

Whew, that’s a lot of settings. Let’s step through them one at a time. We want to restrict the input to valid decimal numbers. Also, since we’re only allowing numbers, it doesn’t make sense to enable autocorrection and capitalization. We may as well turn them off. The Numbers and Punctuation keyboard gives us all the keys we need (all the numbers, a Return key, and—for US English—a period). It actually allows too many characters, so we’ll need to filter our input. We’ll cover that in a bit.

The last two settings will help us create a more streamlined user interface. Essentially, we want to automatically create a new WeightEntry object and switch to the graph view as soon as the user presses the Return key. This reduces the total number of taps needed to enter a new weight. To help support this, we change the Return key’s label to Done to better communicate our intent. More importantly, auto-enabling the Return key means it will be disabled as long as the text field is empty. The system automatically enables the Return key once the user has entered some text. This—when paired with our input filtering—will guarantee that we have a valid weight entry whenever the Return key is pressed.

The interface should now match Figure 4.3. The basics are in place, but we’re going to spice it up a bit.

Figure 4.3

Figure 4.3 Completed enter weight view controller scene

Set Autorotating and Autosizing

We will want our view to rotate into any orientation. There are two steps to this. First, we must modify the controller to allow autorotation.

Open EnterWeightViewController.m and navigate to the shouldAutorotateToInterfaceOrientation: method. This method should return YES if the controller’s view can rotate to the given interfaceOrientation. In the default implementation, it allows rotation only into the right-side-up portrait orientation.

To allow the app to rotate into any orientation, simply have the method return YES, as shown here:

- (BOOL)shouldAutorotateToInterfaceOrientation:
    (UIInterfaceOrientation)interfaceOrientation {
    return YES;
}

However, if you run the app you may notice that autorotation still isn’t working. No matter how you twist the device, the interface remains locked in the portrait orientation.

That’s because there’s a trick. By default, tab views only allow autorotation if all of the contained view controllers support autorotation. Make the same change to the HistoryViewController and the GraphViewController, and then run the app again.

And...it’s still not working. OK, I lied. There were two tricks. When we created a custom tab view, it turns out that we inadvertently overrode the tab bar’s default shouldAutorotateToInterfaceOrientation: implementation. Open TabBarController.m, and delete the shouldAutorotateToInterfaceOrientation: method. This will restore the default behavior. Run it one last time. It should finally rotate as expected. Unfortunately, we have another problem (Figure 4.4). The UI elements are not properly resizing or repositioning themselves in the new, wider view.

Figure 4.4

Figure 4.4 Autorotation works, but autosizing does not.

To fix this, open MainStoryboard.storyboard again. Select the Enter Today’s Weight label, and open the Size inspector. The Autosizing control allows us to lock the object’s position relative to the top, bottom, left, or right sides. It also allows us to automatically stretch and shrink our control horizontally or vertically.

By default, the view is locked to the left and top. We want it locked to the top—but we want it to remain centered in the view. Click the red I-bar on the left side of the Autosizing control to turn it off. After you make changes in the Autosizing control, check the Example preview to make sure the control will behave as you expect. In this case, it should remain centered at the top of the view as the size changes (Figure 4.5).

Figure 4.5

Figure 4.5 Locking the control to the top of the view

We want the Current Date and Time label to stretch so it fills the view from margin to margin. Select the label and then lock it to the top, left, and right sides. Also, turn on the horizontal resizing as shown in Figure 4.6. Use the same settings for the text field as well.

Figure 4.6

Figure 4.6 Stretching the control to fill the view horizontally

Run the app again. Now when it rotates into landscape mode, the app should position and resize the controls appropriately (Figure 4.7).

Figure 4.7

Figure 4.7 Correct autorotation and autosizing

Adding Outlets and Actions

Let’s start by opening EnterWeightViewController.h. We want our controller to conform to the UITextFieldDelegate protocol. This will let us respond to changes in our text field. Modify the header as shown here:

@interface EnterWeightViewController : UIViewController
    <UITextFieldDelegate> {
}

Switch back to MainStoryboard.storyboard and open the Assistant editor. Make sure EnterWeightViewController.h is shown in the second panel. Then right-click the text field, and drag a connection from the delegate to the view controller icon in the scene’s dock (Figure 4.8).

Figure 4.8

Figure 4.8 Connecting the text field’s delegate

Next, drag the Did End On Exit event to the header file and create a new action (Figure 4.9). Name the action saveWeight.

Figure 4.9

Figure 4.9 Creating a new action

Dismiss the connections pop-up window, and Control-drag the text field to the header file (Figure 4.10). Create a new strong outlet named weightTextField.

Figure 4.10

Figure 4.10 Creating a new outlet

Finally, Control-drag from the Current Date and Time label and create a strong outlet named dateLabel.

The header file should now appear as shown here:

#import <UIKit/UIKit.h>
@class WeightHistory;
@interface EnterWeightViewController : UIViewController
<UITextFieldDelegate>
@property (nonatomic, strong) WeightHistory* weightHistory;
@property (strong, nonatomic) IBOutlet UITextField *weightTextField;
@property (strong, nonatomic) IBOutlet UILabel *dateLabel;
- (IBAction)saveWeight:(id)sender;
@end

Creating the Unit Button

Now we want to add a unit button inside the text field. This will allow us to both display the current unit type and change units.

Our unit button is somewhat odd. We won’t be adding it directly to our view hierarchy. Instead, we will programmatically assign it to our text field’s rightView property. This is not something we can do in Interface Builder.

Of course, we could still create the button in Interface Builder and assign it as a new top-level object. We could then use Interface Builder to configure its settings. Unfortunately, this doesn’t really help us, since we won’t be able to visually inspect it as we edit it. All things considered, it’s probably easiest to just create the button in code.

Open EnterWeightViewController.h and add the following property and action:

@property (nonatomic, strong) WeightHistory* weightHistory;

@property (strong, nonatomic) IBOutlet UITextField *weightTextField;
@property (strong, nonatomic) IBOutlet UILabel *dateLabel;
@property (strong, nonatomic) UIButton* unitsButton;
- (IBAction)saveWeight:(id)sender;
- (IBAction)changeUnits:(id)sender;

Next, switch to the implementation file and synthesize the property.

@synthesize unitsButton=_unitsButton;

Now, scroll down to the viewDidLoad method. Modify it as shown:

- (void)viewDidLoad

{
    [super viewDidLoad];
    self.unitsButton = [UIButton buttonWithType:UIButtonTypeCustom];
    self.unitsButton.frame = CGRectMake(0.0f, 0.0f, 25.0f, 17.0f);
    self.unitsButton.backgroundColor = [UIColor lightGrayColor];
    self.unitsButton.titleLabel.font =
    [UIFont boldSystemFontOfSize:12.0f];
    self.unitsButton.titleLabel.textAlignment =
    UITextAlignmentCenter;
    [self.unitsButton setTitle:@"lbs"
                      forState:UIControlStateNormal];
    [self.unitsButton setTitleColor:[UIColor darkGrayColor]
                           forState:UIControlStateNormal];
    [self.unitsButton setTitleColor:[UIColor blueColor]
                           forState:UIControlStateHighlighted];
    [self.unitsButton addTarget:self
                         action:@selector(changeUnits:)
               forControlEvents:UIControlEventTouchUpInside];
    self.weightTextField.rightView = self.unitsButton;
    self.weightTextField.rightViewMode = UITextFieldViewModeAlways;}

We start by creating a custom button. Then we configure it. It’s 17 points tall and 25 points wide, with a light gray background. We also configure its title. It uses a 12-point bold system font and is center aligned. It’s important to center the title; as the user changes units, we will switch the button’s label between “lbs” and “kg.” Centering the label gives the button a nice, consistent appearance, even when the label’s size changes.

Next, we set the default title to “lbs” and then assign text colors for the different control states. Normally the text is dark gray, but when the button is highlighted, the text turns blue.

We also assign the changeUnits: action to our button’s UIControlEventTouchUpInside event. This is identical to drawing a connection between a button’s TouchUpInside event and the desired action in Interface Builder. When the unitsButton is touched, the system will call changeUnits:.

Finally, we assign the unit button to the text field’s rightView property. This will cause it to appear inside the text field along the right side. We then set the view mode so that our button is always visible.

As always, since we assigned the button in viewDidLoad, we should clear it in viewDidUnload. Navigate down to viewDidUnload and add the following line:

self.unitsButton = nil;

Finally, add a method stub for our changeUnits: action. We will flesh out this method after adding the change units view to our storyboard.

- (IBAction)changeUnits:(id)sender {

    // method stub
}

Implementing Actions and Callback Methods

Now switch to EnterWeightViewController.m. Before we start tackling the action and delegate methods, let’s add an extension with a couple of private properties.

@interface EnterWeightViewController()
@property (nonatomic, strong) NSDate* currentDate;
@property (nonatomic, strong) NSNumberFormatter* numberFormatter;
@end

We’re adding two properties: currentDate will hold the current date (based on the date and time when the view appeared), and we will use the numberFormatter to process our user’s input.

Next, synthesize these properties.

@synthesize currentDate = _currentDate;

@synthesize numberFormatter = _numberFormatter;

Then, navigate to the viewDidLoad method. Add the following code to instantiate and configure our number formatter:

- (void)viewDidLoad

{
    [super viewDidLoad];
    self.numberFormatter = [[NSNumberFormatter alloc] init];
    [self.numberFormatter
     setNumberStyle:NSNumberFormatterDecimalStyle];
    [self.numberFormatter
     setMinimum:[NSNumber numberWithFloat:0.0f]];
    self.unitsButton = [UIButton buttonWithType:UIButtonTypeCustom];
    self.unitsButton.frame = CGRectMake(0.0f, 0.0f, 25.0f, 17.0f);
    self.unitsButton.backgroundColor = [UIColor lightGrayColor];
    ...
}

In the last chapter, we used number formatters to create number strings that would format properly regardless of the device’s language and country settings. In this chapter, we will see the other side. We will use the NSNumberFormatter to verify and filter the user’s input. Here, we set it to accept only positive decimal numbers. We will also use the formatter to parse the user input, converting it from a string into a float value.

Again, anything we set up in viewDidLoad needs to be torn down in viewDidUnload. Add the following line:

- (void)viewDidUnload

{
    [self setWeightTextField:nil];
    [self setDateLabel:nil];
    self.unitsButton = nil;
    self.numberFormatter = nil;
    [super viewDidUnload];
}

We still need to reset the screen each time it appears. Remember, a view might be created only once but appear many times. Actually, it’s even more complicated than this. A view may be loaded and unloaded multiple times (usually due to memory shortages). Each time it is loaded, it may appear onscreen more than once. Therefore, it’s important to think things through. Which configuration items need to be performed once and only once? These are typically performed in the application delegate’s application:didFinishLaunchingWithOptions: method. Which configuration items should be performed each time a view loads? These should be performed in the view controller’s viewDidLoad method. Finally, which ones should be performed every time the view appears onscreen? These are done in the viewWillAppear: or viewDidAppear: method.

In our case, we want to update the current date and make sure our text field is ready to receive new information. Implement the viewWillAppear: method as shown:

- (void)viewWillAppear:(BOOL)animated {

    // Sets the current time and date.
    self.currentDate = [NSDate date];
    self.dateLabel.text =
        [NSDateFormatter localizedStringFromDate:self.currentDate
            dateStyle:NSDateFormatterLongStyle
            timeStyle:NSDateFormatterShortStyle];
    // Clear the text field.
    self.weightTextField.text = @"";
    [self.weightTextField becomeFirstResponder];
    [super viewWillAppear:animated];
}

Here we create a new NSDate object set to the current date and time. We then use the NSDateFormatter class method localizedStringFromDate:dateStyle:timeStyle: to produce a properly localized string representation. As you might expect, the formatting of dates and times also varies greatly from country to country and language to language. The NSDateFormatter lets us easily create date strings based on the device’s language and region settings.

Next, we clear the text field and make it the first responder. Making a text field the first responder will automatically display the keyboard. Now, we’ve already linked the text field’s Did End On Exit event to the saveWeight: method. This method will be called whenever the keyboard’s Done button is pressed.

As we described earlier, this provides a very streamlined system for entering the weights. When the user opens this view, the text field is automatically selected and the keyboard is ready and waiting. The user just types in the weight value and presses Done. They don’t need to select the text box or press a Save button. Everything is simple, automatic, and clean.

However, it does create one small problem. The keyboard covers our tab bar. This prevents our users from navigating away from this screen without entering a new weight.

Obviously, this is not ideal. We need to provide a way (preferably something intuitive and non-intrusive) to dismiss the keyboard, giving us access to the tab bar again. Let’s add a gesture recognizer that responds to a simple down swipe.

Open MainStoryboard.storyboard again. Drag a swipe gesture recognizer from the library and drop it onto the enter weight view controller scene’s main view (Figure 4.12).

Figure 4.12

Figure 4.12 Adding a swipe gesture recognizer

The gesture recognizer will appear in the scene’s dock. Select it and open the Attributes inspector. Set the Swipe attribute to Down. Leave the Touches attribute at 1. This will now trigger on a single-finger, downward swipe.

If we’re going to recognize downward swipes, we should recognize upward swipes as well. So, let’s add a second recognizer. Drag out another swipe gesture recognizer and add it to the view. Set its Swipe attribute to Up. Leave the Touches attribute at 1.

Now open the Assistant editor, and make sure the EnterWeightViewController.h file is showing. Right-click and drag from the down swipe gesture recognizer to just below the declaration of our changeUnits: method (Figure 4.13). Change the Connection to Action, and name it handleDownwardSwipe. Then do the same for the up gesture recognizer. Name its action handleUpwardSwipe.

Figure 4.13

Figure 4.13 Connecting a gesture recognizer

Then create an outlet for each gesture recognizer. Control-drag from the recognizer to the header file. Name the first outlet downSwipeRecognizer. Name the second upSwipeRecognizer.

Now switch to EnterWeightViewController.m and implement the actions:

- (IBAction)handleDownwardSwipe:(id)sender {

    // Get rid of the keyboard.
    [self.weightTextField resignFirstResponder];
}
- (IBAction)handleUpwardSwipe:(id)sender {
    // display keyboard
    [self.weightTextField becomeFirstResponder];
}

The handleDownwardSwipe method simply has the text field resign as first responder. Just as before, the keyboard is automatically linked to the first responder. When the text field resigns, the keyboard disappears. The handleUpwardSwipe method is just the inverse of that. It assigns the text field as the first responder, causing the keyboard to appear again. Of course, the user could do the same thing by simply tapping the text field, but many users will automatically try to undo a downward swipe with an upward swipe. Adding the inverse operation makes the interface feel more complete.

While this is an elegant solution, it brings up a common problem with iOS development. We can easily build complicated touch-, gesture-, and motion-based controls (see Chapter 8 for more examples), but how do we make sure the user knows they exist? iOS applications usually don’t have help screens, and—in my experience—few users actually read the help information that does exist.

For example, you might create a great three-finger swipe that radically simplifies your application’s workflow. However, unless your users stumble upon it by accident, most will never know it exists. That’s not to say that you should avoid using unusual gestures. On the contrary, many applications use novel gestures to great effect. The Twitter app is an excellent example: You scroll through the table view of incoming tweets. When you get to the top, you just pull down to check for new messages.

This is a brilliant gesture. Users will almost certainly stumble upon it as they accidentally try to scroll past the end of their tweets. More importantly, once you find it, the gesture is so natural that it quickly becomes part of your regular workflow.

The bottom line is that successfully communicating how your app operates can be one of the biggest challenges in iOS development. Typically, this involves extensive usability testing to make sure your interface is as intuitive and natural as possible.

OK, let’s switch gears and tackle the saveWeight: action.

#pragma mark – Action Methods

- (IBAction)saveWeight:(id)sender {
    // Save the weight to the model.
    NSNumber* weight = [self.numberFormatter
                        numberFromString:self.weightTextField.text];
    WeightEntry* entry = [[WeightEntry alloc]
                          initWithWeight:[weight floatValue]
                          usingUnits:self.weightHistory.defaultUnits
                          forDate:self.currentDate];
    [self.weightHistory addWeight:entry];
    // Automatically move to the second tab.
    // Should be the graph view.
    self.tabBarController.selectedIndex = 1;
}

First, we parse the text field to extract the weight’s floating point value. Normally you want to check numberFromString:’s return value. If the number does not match the specified format, this method will return nil. However, in this case we know that the text field can only have valid values. The saveWeight: action is only triggered when the keyboard’s Done button is pressed, and the Done button only becomes active when our text field contains text. Since we will be filtering the user input, this text can only contain a valid decimal number.

Next, we instantiate a new WeightEntry object using this weight value, our defaultUnits, and the currentDate property (if you remember, currentDate was set when the enter weight view appeared onscreen). We add this entry to our model.

Finally, we change the tab bar’s selected controller. This will automatically move us to the second tab—currently set to the graph view. Again, we are trying to make entering new weights as streamlined as possible. For the most part, this means removing unnecessary touches. Users will typically enter only one weight at a time. Therefore, we should streamline their interaction and automatically bring up the weight trends graph after each new value.

We’re going to skip the changeUnits: method for now. We’ll get back to it in the “Changing Weight Units” section. Instead, let’s begin filtering the user’s input.

Filtering Keyboard Input

The UITextFieldDelegate protocol has a number of optional methods that we can use to monitor and control our text field. In particular, we will implement textField:shouldChangeCharactersInRange:replacementString: to filter the user input. Implement the method as shown:

#pragma mark - Delegate Methods

- (BOOL)textField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)string {
    // It's OK to hit return.
    if ([string isEqualToString:@"\n"]) return YES;
    NSString* changedString =
    [textField.text stringByReplacingCharactersInRange:range
                                            withString:string];
    // It's OK to delete everything.
    if ([changedString isEqualToString:@""]) return YES;
    NSNumber* number =
    [self.numberFormatter numberFromString:changedString];
    // Filter out invalid number formats.
    if (number == nil) {
        // We might want to add an alert sound here.
        return NO;
    }
    return YES;
}

This method is called whenever the user presses a button on the keyboard (including the backspace button). If we return YES, the change is made. If we return NO, the change is canceled.

We start by checking to see if the user hit the Return key. Since this is the trigger for our saveWeight: action, we need to accept it.

Next, we create a new string by applying the proposed change to textField’s current contents. If the resulting string represents a valid decimal number, we accept the change. Otherwise, we reject it.

Of course, it’s not quite that simple. First, we have to deal with another corner case. If the resulting string is empty, we allow the change. Technically, an empty string is not a valid decimal number; however, we really want to let the users delete all the characters, just in case they made a typing mistake and want to start over.

If the string is not empty, we use our numberFormatter to parse our string. Again, we use the numberFromString: method. If the string does not match the expected format, this method returns nil. We simply check the return value and return YES or NO as appropriate.

Technically, we could simplify the code and just return the result from parsing the string as shown here:

return [self.numberFormatter numberFromString:changedString];

However, we may want to add an alert sound or other feedback to the user. Using the more verbose version of the code will make those additions easier.

Run the application. The text field should appear with the embedded unit button. Check to make sure the input filtering works correctly. When you press the Done button, the view should switch to the graph view. Nothing shows up yet (of course), but the transition should work. Go back to the enter weight view. The system should automatically clear and select the text field. The keyboard should be visible. Swipe down to dismiss the keyboard. Swipe up to re-enable it. You can even tap the units button, but it won’t do anything yet. We’ll fix that next (Figure 4.14).

Figure 4.14

Figure 4.14 The completed enter weight view

Peachpit Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from Peachpit and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about Peachpit products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites; develop new products and services; conduct educational research; and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email ask@peachpit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by Adobe Press. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.peachpit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020