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

Home > Articles

This chapter is from the book

Changing Weight Units

When the user presses the units button, we need to open a new view and let them change the default units. To do this, we will use a modal view. Modal views are perfect for presenting focused, short-term tasks that the user must either complete or explicitly cancel.

Let’s start by adding another UIViewController subclass. Name it UnitSelectorViewController and place it in the Controllers group (if you need help, follow the step-by-step instructions in “Configuring the Tab Bar” in Chapter 3).

Now open MainStoryboard.storyboard. Drag out a new UIViewController object and place it next to our enter weight view controller scene. Switch to the Identity inspector, and change its Class setting so that it uses our new UnitSelectorViewController class.

Ideally, we want a segue from our changeUnits: action to the unit selector view. Unfortunately, we cannot draw segues from actions directly. Instead, let’s create a segue that we can manually call from our code.

Control-drag from the enter weight view controller icon to our new scene. In the pop-up window, select modal. This creates a generic modal storyboard segue. This sets the segue between the two scenes. Select the segue and switch to the Attributes inspector. Set the Identifier attribute to Unit Selector Segue, and the Transition attribute to Flip Horizontal. Now we just need to trigger this segue from our changeUnits: action.

Switch back to EnterWeightViewController.m. Let’s start by defining a string constant for our segue’s identifier. Add the following line before the @implementation block:

static NSString* const UNIT_SELECTOR_SEGUE = @"Unit Selector Segue";

Now navigate down to the changeUnits: action. We just need to call our controller’s performSegueWithIdentifier:sender: method.

- (IBAction)changeUnits:(id)sender {
    [self performSegueWithIdentifier:
     UNIT_SELECTOR_SEGUE sender:self];
}

This will trigger our segue. Our enter weight view will flip over and reveal the new unit selector view.

So, let’s design that view. Open the storyboard again, and zoom in on our unit selector scene. Select the view and change its Background attribute to View Flipside Background Color. Next, drag a picker view from the Object library and place it at the top of the view. In the Size inspector, make sure it is locked to the left, right, and top and that it scales horizontally.

Next, drag out a button and place it at the bottom of the view. Stretch it so that it fills the view from margin to margin. Its autosizing settings should lock it to the left, bottom, and right, with horizontal scaling enabled. Finally, set its Title attribute to Done.

We ideally want a colored button. The iOS 5 SDK gives us a number of functions for customizing the appearance of our controls. Unfortunately, this doesn’t include setting a button’s background color. There are a number of ways to work around this. For example, many developers create stretchable background images for their buttons. However, this does not give us very much control at runtime. We could create a UIButton subclass and provide custom drawing code—but that’s a lot of work. Instead, we’ll modify the button’s appearance using Core Animation (while also looking at some of the problems with this approach).

Change the button’s Type attribute to Custom, and set the Background attribute to a dark green. I selected Clover from the crayon box color selector. Click the Background attribute. When the pop-up menu appears, select Other. Make sure the crayon box tab is selected, and then choose the Clover crayon (third from the left on the top row).

Finally, set the Text Color attribute to Light Text Color.

The interface should now match Figure 4.15. Everything looks OK—except for the square corners on our Done button. We’ll fix that shortly. In the meantime, let’s set up our outlets and actions.

Figure 4.15

Figure 4.15 The unit selector view

First, open UnitSelectorViewController.h. This class needs to adopt both the UIPickerViewDelegate and the UIPickerViewDataSource protocols.

@interface UnitSelectorViewController : UIViewController
    <UIPickerViewDelegate, UIPickerViewDataSource> {
}
@end

Then switch back to the storyboard file, and open the Assistant editor. Make sure UnitSelectorViewController.h is showing. Control-drag the picker view to the header file, and create a strong outlet named unitPickerView. Next, Control-drag the button twice. First, create a strong outlet named doneButton. Then create an action named done. Make sure its Event is set to Touch Up Inside.

Now we need to link the picker view to its delegate and data source. Right-click the picker view, and then drag from the pop-up window’s delegate outlet to the view controller icon in the scene’s dock (Figure 4.16). Next, drag the pop-up’s dataSource outlet to the view controller as well.

Figure 4.16

Figure 4.16 Connecting the delegate

Defining the View Delegate

Switch back to the Standard editor and the UnitSelectorViewController.h file. It should now appear as shown here:

#import <UIKit/UIKit.h>
@interface UnitSelectorViewController : UIViewController
<UIPickerViewDelegate, UIPickerViewDataSource>
@property (strong, nonatomic) IBOutlet UIPickerView *unitPickerView;
@property (strong, nonatomic) IBOutlet UIButton *doneButton;
- (IBAction)done:(id)sender;
@end

We still need to make a few additional changes. Start by importing our WeightEntry class, and add a forward declaration for the UnitSelectorViewControllerDelegate protocol before its @interface block.

#import <UIKit/UIKit.h>
#import "WeightEntry.h"
@protocol UnitSelectorViewControllerDelegate;
@interface UnitSelectorViewController :
UIViewController <UIPickerViewDelegate, UIPickerViewDataSource> {
...

Now let’s declare two additional properties: one for our delegate, the other for our default unit.

@property (strong, nonatomic) IBOutlet UIButton *doneButton;
@property (weak, nonatomic) id<UnitSelectorViewControllerDelegate>
    delegate;
@property (assign, nonatomic) WeightUnit defaultUnit;
- (IBAction)done:(id)sender;

Finally, we need to define our protocol. Add the following, after the @interface block:

@protocol UnitSelectorViewControllerDelegate <NSObject>
- (void)unitSelectorDone:(UnitSelectorViewController*)controller;
- (void)unitSelector:(UnitSelectorViewController*)controller
        changedUnits:(WeightUnit)unit;
@end

That’s it. We’re done with the interface. Now we need to implement these methods.

Implementing the Controller

Open UnitSelectorViewController.m and synthesize the delegate and the defaultUnit.

@synthesize delegate = _delegate;

@synthesize defaultUnit = _defaultUnit;

Now we want to automatically select the current default unit when our view loads. To do this, uncomment viewDidLoad and make the following changes:

#pragma mark - View lifecycle
- (void)viewDidLoad
{
    [super viewDidLoad];
    // Set the default units.
    [self.unitPickerView selectRow:self.defaultUnit
                       inComponent:0
                          animated:NO];}

Remember, our WeightUnit enum values are assigned sequentially starting with 0. Our picker view is also zero-indexed, with exactly one row for each WeightUnit value. This means we can use WeightUnits and row indexes interchangeably. Each row index maps to a corresponding WeightUnit. Here, we simply select the row that corresponds with the default unit value.

Next, we need to enable autorotation to any orientation. As before, simply have the shouldAutorotateToInterfaceOrientation: method return YES.

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

Now let’s implement the done: action. We just call the delegate’s unitSelectorDone: method, as shown here:

- (IBAction)done:(id)sender {
    [self.delegate unitSelectorDone:self];
}

OK, we’re almost done with this controller. We still need to implement the UIPickerViewDataSource methods:

#pragma mark - UIPickerViewDataSource Methods
- (NSInteger)numberOfComponentsInPickerView:
(UIPickerView *)pickerView {
    return 1;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView
    numberOfRowsInComponent:(NSInteger)component {
    return 2;
}

The numberOfComponentsInPickerView: method simply returns the number of components that our picker view will use. Each component represents a separate settable field. For example, a date picker has three components: month, day, and year. In our case, we only need a single component.

The pickerView:numberOfRowsInComponent: method returns the number of rows (or possible values) for the given component. We know that our picker view only has a single component, so we don’t need to check the component argument. Since we only have two possible values, pounds and kilograms, we can just return 2.

Now let’s look at the delegate methods:

#pragma mark - UIPickerViewDelegate Methods
- (NSString *)pickerView:(UIPickerView *)pickerView
             titleForRow:(NSInteger)row
            forComponent:(NSInteger)component {
    return [WeightEntry stringForUnit:row];
}
- (void)pickerView:(UIPickerView *)pickerView
      didSelectRow:(NSInteger)row
       inComponent:(NSInteger)component {
    [self.delegate unitSelector:self changedUnits:row];
}

The pickerView:titleForRow:forComponent: method should return the title that will be displayed for the given row and component. In our case, we can map the rows directly to the WeightUnit enum values and simply call stringForUnit: to generate the correct string (@"lbs" or @"kg").

Meanwhile, the pickerView:didSelectRow:inComponent: method is called whenever the user changes the current selection. Here, we simply call the delegate’s unitSelector:changedUnits: method, passing in the row value. Again, our row values correspond directly to the appropriate WeightUnit values.

Passing Data Back and Forth

We still need to pass data into and out of our modal view. Start by opening EntryWeightViewController.h. We need to import UnitSelectorViewController.h and declare that EnterWeightViewController will adopt the UnitSelectorViewControllerDelegate protocol.

#import <UIKit/UIKit.h>
#import "UnitSelectorViewController.h"
@class WeightHistory;
@interface EnterWeightViewController : UIViewController
<UITextFieldDelegate, UnitSelectorViewControllerDelegate> {

Now switch to the implementation file. We trigger the modal segue in our changeUnits: method—but we cannot set the default unit value there. Our destination view controller may not exist yet. Instead, we wait for the prepareForSegue:sender: method—just as we did in Chapter 3.

- (void)prepareForSegue:(UIStoryboardSegue *)segue
                 sender:(id)sender {
    if ([segue.identifier isEqualToString:UNIT_SELECTOR_SEGUE]) {
        UnitSelectorViewController* unitSelectorController =
            segue.destinationViewController;
        unitSelectorController.delegate = self;
        unitSelectorController.defaultUnit =
           self.weightHistory.defaultUnits;
    }
}

Here, we check the segue’s identifier, just to make sure we have the correct segue. Then we grab a reference to our UnitSelectorViewController, and we set both the delegate and the default unit value.

To get data from our modal view, we simply implement the UnitSeletorViewControllerDelegate methods. Let’s start with unitSelector:changedUnits:.

-(void)unitSelector:(UnitSelectorViewController*) sender
       changedUnits:(WeightUnit)unit {
    self.weightHistory.defaultUnits = unit;
    [self.unitsButton setTitle: [WeightEntry stringForUnit:unit]
                      forState:UIControlStateNormal];}

This method is called whenever the user changes the units in the UnitSelectorViewController. Here, we tell the model to change its default units and then update the title in our unit button. Again, we only update the title for the UIControlStateNormal. All other control states will default back to this setting.

Now let’s look at unitSelectorDone:.

-(void)unitSelectorDone:(UnitSelectorViewController*) sender {
    [self dismissModalViewControllerAnimated:YES];
}

This method is called when the user presses the UnitSelectorViewController’s Done button. Note that we could have dismissed the modal view within UnitSelectorViewController’s done: method by calling [self.parentViewController dismissModalViewControllerAnimated:YES]. However, the pattern we’re using here is generally best.

Passing control back to the parent view through a delegate method and then letting the parent view dismiss the modal view may take a bit more code, but it also gives us additional flexibility. For example, our parent controller might want to access the delegate view’s properties before dismissing it. Or we may want to perform some postprocessing after dismissing the modal view. We can easily add these features in our delegate method. Additionally, it just feels cleaner. If a class presents a modal view, then it should also dismiss that view. Splitting the presentation and dismissal code into different classes makes everything just a little harder to follow.

And this isn’t an entirely academic argument. As our code is currently written, we will change our default unit value whenever the user changes the value in the picker view. However, this is not necessarily the best approach. We may want to wait until the user presses the Done button, and then set the default unit value based on their final selection. With the delegate methods in place, we can easily change our implementation based on actual performance testing. More importantly, we can change this behavior in our EnterWeightViewController class; we don’t need to touch our modal view at all.

Run the application. You should be able to press the unit button and bring up the unit selector view. Change the units to kilograms and press Done. The button’s title should change from “lbs” to “kg.” There’s only one problem remaining: Our Done button still looks chunky. Let’s fix that.

Rounding Corners with Core Animation

Most of the time when you talk about Core Animation, you’re talking about smoothly moving user interface elements around the screen, having them fade in and out, or flipping them over. Here, however, we will hijack some of the more obscure features of Core Animation to round off our button’s corners, add a border, and layer over a glossy sheen.

As you might guess, Core Animation is a deep and complex subject. We will look at techniques for animating view properties in “Managing Pop-Up Views” in Chapter 8. However, even that only scratches the surface. If you want to get all the gory details, I recommend reading the Core Animation Programming Guide in Apple’s documentation.

First things first, we need to add the QuartzCore framework to our project. Click the blue Health Beat icon to bring up the project settings. Make sure the Health Beat target is selected, and click the Build Phases tab. Next, expand the Link Binary With Libraries build phase, and click the plus button to add another framework. Scroll through the list and add QuartzCore.framework (Figure 4.17).

Figure 4.17

Figure 4.17 Adding a new framework

Next, open UnitSelectorViewController.m. We need to import the QuartzCore header.

#import <QuartzCore/QuartzCore.h>

Then navigate to the viewDidLoad method. Modify it as shown:

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Set the default units.
    [self.unitPickerView selectRow:self.defaultUnit
                       inComponent:0
                          animated:NO];
    //Build our gradient overlays.
    CAGradientLayer* topGradient = [[CAGradientLayer alloc] init];
    topGradient.name = @"Top Gradient";
    // Make it half the height.
    CGRect frame = self.doneButton.layer.bounds;
    frame.size.height /= 2.0f;
    topGradient.frame = frame;
    UIColor* topColor = [UIColor colorWithWhite:1.0f alpha:0.75f];
    UIColor* bottomColor = [UIColor colorWithWhite:1.0f alpha:0.0f];
    topGradient.colors = [NSArray arrayWithObjects:
                          (__bridge id)topColor.CGColor,
                          (__bridge id)bottomColor.CGColor, nil];
    CAGradientLayer* bottomGradient =
    [[CAGradientLayer alloc] init];
    bottomGradient.name = @"Bottom Gradient";
    // Make it half the size.
    frame = self.doneButton.layer.bounds;
    frame.size.height /= 2.0f;
    // And move it to the bottom.
    frame.origin.y = frame.size.height;
    bottomGradient.frame = frame;
    topColor = [UIColor colorWithWhite:0.0f alpha:0.20f];
    bottomColor = [UIColor colorWithWhite:0.0f alpha:0.0f];
    bottomGradient.colors = [NSArray arrayWithObjects:
                             (__bridge id)topColor.CGColor,
                             (__bridge id)bottomColor.CGColor, nil];
    // Round the corners.
    [self.doneButton.layer setCornerRadius:8.0f];
    // Clip sublayers.
    [self.doneButton.layer setMasksToBounds:YES];
    // Add a border.
    [self.doneButton.layer setBorderWidth:2.0f];
    [self.doneButton.layer
     setBorderColor:[[UIColor lightTextColor] CGColor]];
    // Add the gradient layers.
    [self.doneButton.layer addSublayer:topGradient];
    [self.doneButton.layer addSublayer:bottomGradient];
}

There’s a lot going on here, so let’s step through it slowly. This code modifies our button’s Core Animation layer. The CALayer is a lightweight object that encapsulates the timing, geometry, and visual properties of a view. In UIKit, a CALayer backs each UIView (and therefore, anything that inherits from UIView). Because of the tight coupling between layers and views, we can easily access and change the visual properties contained in our button’s layer.

We start by creating a CAGradientLayer and giving the layer a name. We will use this name to identify our gradient layer in later methods. Next, we calculate the frame for this layer. We start with the button layer’s bounds, but we divide the height in half. Remember, the frame is the object’s coordinates and size in the containing view or layer’s coordinate system. The bounds represent the object’s coordinates and size in its own coordinate system. In other words, the origin is almost always set to {0.0f, 0.0f} (there are situations where you might use a non-zero origin as an offset, for example when clipping part of an image, but these cases are rare). Using the superlayer’s bounds for the sublayer’s frame means the sublayer will fill the superlayer completely. By dividing the height in half, we end up with a sublayer that will cover just the top half of the main layer.

CAGradientLayers accept an NSArray filled with CGColorRefs. By default, it creates a linear, evenly spaced gradient that transitions from one color to the next. We will just pass in two colors, which will represent the two end points.

Note that an NSArray technically only accepts pointers to Objective-C objects. A CGColorRef is simply a pointer to a CGColor structure—definitely not an Objective-C object. However, we can cast them into id objects to get around the compiler warnings. It’s a bit wacky, but we do what we have to do.

For the curious, this works because NSArray is toll-free bridged with the Foundation CFArray class (under the surface, they are the same objects). While NSArrays are only used to store Objective-C objects, CFArrays can be used to store any arbitrary pointer-sized data. In fact, the CFArrayCreate() method includes parameters that define how the objects are (or are not) retained when placed in the array. When we create an NSArray, we are really creating a CFArray that uses ARC for memory management, which is good. Our CGColors came from an Objective-C method call—so ARC is already managing their memory (see “ARC and Toll-Free Bridging” in Chapter 2 for more information).

As a result, this trick requires considerably less typing than creating a CFArray directly. We do need to use the __bridge annotation to tell ARC that we’re not transferring the references’ ownership, but other than that, memory management works as expected.

In our code, we create two colors. One is white with a 75 percent alpha. The other is completely transparent. We place these into an array and pass the array to the gradient. The CAGradientLayer then makes a smooth, linear transition that slowly fades out as you move down the screen. This adds a highlight to the top of our button.

We do the same thing for the bottomGradient. The only difference is that we increase its origin’s y value to position it on the bottom half of the button. We also use black colors whose alpha values will transition between 20 percent and completely clear. These will slightly darken the bottom half of our control.

Next, we set the corner radius, thus rounding the corners. We then clip our drawing to the area bounded by our rounded corners. This will clip our gradient sublevels as well.

Then we give our button a 2-pixel border, whose color matches the button’s title color. Notice, however, that whereas the button used a UIColor object, the CALayer uses CGColor structures. Again, we can request a CGColor reference from our UIColor object.

Run the application again. Now when you press the unit button, our modal view’s Done button looks all fancy (Figure 4.18).

Figure 4.18

Figure 4.18 Done button with rounded corners

This works fine in portrait mode, but if you rotate the view, you’ll notice that our gradients don’t stretch to fill the button. The problem is, we cannot automatically resize these layers. Instead, we need to manually resize them whenever the system lays out our views. To do this, implement the view controller’s viewDidLayoutSubviews method.

- (void)viewDidLayoutSubviews {

    CALayer* layer = self.doneButton.layer;
    CGFloat width = layer.bounds.size.width;
    for (CALayer* sublayer in layer.sublayers) {
        if ([sublayer.name hasSuffix:@"Gradient"]) {
            CGRect frame = sublayer.frame;
            frame.size.width = width;
            sublayer.frame = frame;
        }
    }
}

This method is called right after the system lays out all our subviews. Here, we just grab a reference to our doneButton’s layer and the layer’s width. We then iterate over all the sublayers. If the sublayer has a name that ends with “Gradient,” we resize it to match the button’s width. This way our custom layers will be resized, but we won’t alter any of the button’s other layers.

Try it out. Switch back and forth between the enter weight and unit select views. Rotate the interface to all the different orientations. If everything is working, commit all our changes. Next stop, the history 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