MDC-111 iOS: Incorporating Material Components into your codebase (Objective-C)

1. Introduction

logo_components_color_2x_web_96dp.png

Material Components (MDC) help developers implement Material Design. Created by a team of engineers and UX designers at Google, MDC features dozens of beautiful and functional UI components and is available for Android, iOS, web and Flutter.material.io/develop

Material Components (MDC) isn't a new coding system that requires a paradigm shift in your app. MDC is built on the same classes and APIs you already know in iOS. MDC can be added as needed to your existing app. Some components can make an immediate design improvement to your app.

What you'll build

In this codelab, you'll replace some existing components in a form with new ones by MDC.

MDC-iOS components in this codelab:

  • Text fields
  • Buttons
  • Alerts

How would you rate your level of experience building iOS apps?

Novice Intermediate Proficient

2. Set up your development environment

Download the starter codelab app

The starter app is located in the material-components-ios-codelabs-master/MDC-111/ObjectiveC/Starter directory. Be sure to cd into that directory before beginning.

...or clone it from GitHub

To clone this codelab from GitHub, run the following commands:

git clone https://github.com/material-components/material-components-ios-codelabs
cd material-components-ios-codelabs/MDC-111/ObjectiveC/Starter

Run the starter app

1. In Xcode open the workspace "MDC-111.xcworkspace". If you see the "Welcome to Xcode" window, click "Open another project...", navigate to the file and click open.

OR: Go to File > Open... and navigate to the file and click "Open".

OR: At the terminal enter:
open MDC-111.xcworkspace

2. Run the app by pressing the play button at the top of the window.Note: Screenshots are generated from building on an iPhone 6s.

Success! You should see the app and its form.

aa5010084d6dc845.png

3. Update the storyboard and view controller

Material Design text fields have a major usability gain over plain text fields. By defining the hit zone with an outline or a background fill, users are more likely to interact with your form or identify text fields within more complicated content.

Remove dividers

The file Main.storyboard has been setup to mimic a common iOS form design. You might recognize if from email clients or as part of tables you'd find in Settings. The first thing we need to change is removing the dividers.

In Main.storyboard, navigate to the Shipping Address Scene and expand all the arrows until you see the dividers and the textfields.

63cf64ad81755e6e.png

Delete each of the dividers.

628774010451933a.png

Change the text field class in the storyboard

Select each text field. Open the Identify Inspector on the right. (If you can't find the Identity Inspector, you can access it in View > Utilities > Show Identity Inspector.)

In the Custom Class field, type MDCTextField. Do this for all 5 text fields.

e08e1df90428c103.png

Import MDC text fields in the view controller

At the top of ViewController.m, add the following above @interface ViewController () <UITextFieldDelegate>:

#import "MaterialTextFields.h"

Change the text field class in the view controller

In ViewController.m, change each UITextField to MDCTextField.

@property(weak, nonatomic) IBOutlet MDCTextField *name;
@property(weak, nonatomic) IBOutlet MDCTextField *address;
@property(weak, nonatomic) IBOutlet MDCTextField *city;
@property(weak, nonatomic) IBOutlet MDCTextField *state;
@property(weak, nonatomic) IBOutlet MDCTextField *zip;

Add text input controllers

MDCTextField is subclassed from UITextField. It has a lot of capabilities but requires a controller to style it. Those controllers have to stay in memory so let's add them as properties of the view controller.

In ViewController.m, add the following below @interface ViewController () <UITextFieldDelegate>:

@property(nonatomic) MDCTextInputControllerOutlined *nameController;
@property(nonatomic) MDCTextInputControllerOutlined *addressController;
@property(nonatomic) MDCTextInputControllerOutlined *cityController;
@property(nonatomic) MDCTextInputControllerOutlined *stateController;
@property(nonatomic) MDCTextInputControllerOutlined *zipController;

Now we initialize them in viewDidLoad.

In ViewController.m, add the following to viewDidLoad under [super viewDidLoad]:

self.nameController = [[MDCTextInputControllerOutlined alloc] initWithTextInput:self.name];
self.addressController = [[MDCTextInputControllerOutlined alloc] initWithTextInput:self.address];
self.cityController = [[MDCTextInputControllerOutlined alloc] initWithTextInput:self.city];
self.stateController = [[MDCTextInputControllerOutlined alloc] initWithTextInput:self.state];
self.zipController = [[MDCTextInputControllerOutlined alloc] initWithTextInput:self.zip];

Import MDC text field themers in the view controller

At the top of ViewController.m, add the following above @interface ViewController () <UITextFieldDelegate>:

#import "MaterialTextFields+ColorThemer.h"
#import "MaterialTextFields+TypographyThemer.h"

Apply the MDC text field themers

At the top of ViewController.m, add the following to viewDidLoad underneath the MDCTextInputController initializations:

MDCSemanticColorScheme *colorScheme = [[MDCSemanticColorScheme alloc] init];
[MDCTextFieldColorThemer applySemanticColorScheme:colorScheme
                            toTextInputController:self.nameController];
[MDCTextFieldColorThemer applySemanticColorScheme:colorScheme
                            toTextInputController:self.addressController];
[MDCTextFieldColorThemer applySemanticColorScheme:colorScheme
                            toTextInputController:self.cityController];
[MDCTextFieldColorThemer applySemanticColorScheme:colorScheme
                            toTextInputController:self.stateController];
[MDCTextFieldColorThemer applySemanticColorScheme:colorScheme toTextInputController:self.zipController];
MDCTypographyScheme *typographyScheme = [[MDCTypographyScheme alloc] init];
[MDCTextFieldTypographyThemer applyTypographyScheme:typographyScheme
                              toTextInputController:self.nameController];
[MDCTextFieldTypographyThemer applyTypographyScheme:typographyScheme
                              toTextInputController:self.addressController];
[MDCTextFieldTypographyThemer applyTypographyScheme:typographyScheme
                              toTextInputController:self.cityController];
[MDCTextFieldTypographyThemer applyTypographyScheme:typographyScheme
                              toTextInputController:self.stateController];
[MDCTextFieldTypographyThemer applyTypographyScheme:typographyScheme
                              toTextInputController:self.zipController];

Build and run:

c5f520cdd896fcd8.png

The text fields are all updated to use the newer designs in MDC. Let's change the trailing constraint to be something more appropriate for this design.

Update trailing constraint constant

In the storyboard, click on the Safe Area in the view hierarchy and open the Size Inspector. (If you can't find the Size Inspector, you can open it in View > Utilities > Show Size Inspector.)

e8f8053a93408deb.png

Double click or press Edit on the constraint called "Align Trailing to Name". Change the constant value to be 20 instead of 0.

3745ae72d783900c.png

Build and run:

98b9d7fa8f289dd7.png

Let's refine the layout even more. Change the Safe Area's leading constraint constant to the Name text field to also be 20 instead of 32.

741d7ab7d74f06ef.png

Build and run: 10c5fb4eeb41424c.png

The layout is perfect!

Add an error

MDC text fields have built in error presentation. MDC adds red text beneath your text field and updates decorations to be red too.

In ViewController.m, add the following anywhere in viewDidLoad:

self.zip.delegate = self;

Then, also in ViewController.m, add the following method under viewDidLoad:

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
  NSString *finishedString =
    [textField.text stringByReplacingCharactersInRange:range withString:string];

  if (textField == self.zip) {
    if ([finishedString rangeOfCharacterFromSet:[NSCharacterSet letterCharacterSet]].length > 0) {
      [self.zipController setErrorText:@"Error: Zip can only contain numbers"
               errorAccessibilityValue:nil];
    } else {
      [self.zipController setErrorText:nil errorAccessibilityValue:nil];
    }
  }
  return YES;
}

Build and run. Enter a letter in the zip field:

42b3f1d8ef2c45d7.png

The zip text field is red and has error text beneath it. To get rid of the error, just replace the letter with a number.

4. Update the button

MDC has buttons with:

  • Ink ripples
  • Rounded corners
  • Theming support
  • Beautiful shadows
  • Pixel-perfect layout and typography

They are built on UIButton (the standard iOS button class), so you already know how to use them in your code.

Import MDC buttons in the view controller

At the top of ViewController.m, add the following above @interface ViewController () <UITextFieldDelegate>:

#import "MaterialButtons.h"

Change button class

In ViewController.m, in the properties section, change the class of the UIButton *saveButton IBOutlet :

@property (weak, nonatomic) IBOutlet MDCButton *saveButton;

In the storyboard, select the save button and open the Identify Inspector on the right. (If you can't find the Identity Inspector, you can access it in View > Utilities > Show Identity Inspector.)

In the Custom Class field, type MDCButton:

2a38a73a8b4e0ada.png

Still in the storyboard, open the Attributes Inspector. (If you can't find the Attributes Inspector, you can access it in View > Utilities > Show Attributes Inspector.)

Set the button type to custom:

52d6e6cb2a2e6c3.png

Import the button themer

At the top of ViewController.m, add the following below #import "MaterialButtons.h"

#import "MaterialButtons+ButtonThemer.h"

Theme the button

In ViewController.m, add the following to viewDidLoad under [super viewDidLoad]:

[MDCContainedButtonThemer applyScheme:[[MDCButtonScheme alloc] init] toButton:self.saveButton];

Make the button text uppercase

Material Design recommends you make the button text uppercase.

In ViewController.m, add the following to viewDidLoad under [super viewDidLoad]:

[self.saveButton setTitle:@"Save" forState:UIControlStateNormal];

Make the button wide

Material Design recommends buttons be 88 points wide or more.

In the storyboard, selected the button. We're going to make a width constraint. To do this, select the save button and then press the add new constraints button in the bottom right corner of the canvas. It looks like a little tie fighter:

88c871b3cb0dc64a.png

Select the checkbox for width and set the constant to 88 and then click away from the dialog to close it:

a1e55991ec66ceee.png

Since we want it to be at least 88, we need to make this a greater-than-or-equal-to constraint. Select the save button and open the Size Inspector. (If you can't find the Size Inspector, you can open it in View > Utilities > Show Size Inspector.)

Select the width constraint and click edit. Change the = to >=:

fb6d76aafacbd24d.png

Click away from the dialog. Build and run:

32571e7ba6b9e6c8.png

The button is fresher and fits in with the style of your text fields. But if you tap it, you may get a warning in the console, "Button touch target does not meet minimum size guidelines of (48, 48)." That's a great point. We do want to make sure our buttons are tappable by all finger sizes and by people with motor disabilities.

Add hit area insets

In ViewController.m, update the following method to account for hitAreaInsets:

- (void)viewDidLayoutSubviews {
  [super viewDidLayoutSubviews];
 
  // TODO: Update @c hitAreaInsets.
  CGFloat verticalInset = MIN(0, (CGRectGetHeight(self.saveButton.bounds) - 48) / 2); 
  self.saveButton.hitAreaInsets = UIEdgeInsetsMake(verticalInset, 0, verticalInset, 0);
}

Build and run and tap the save button. There's no warning in the console! And you can tap a little outside the button's frame and still trigger a touch.

5. Update alerts

Alerts are a familiar way to present errors, mandatory choices, and one-line forms. But UIAlertController is limited in the amount of text it can show on an iPad.

4509f505799a2d96.png

Import MDC alerts

At the top of ViewController.m, add the following above @interface ViewController () <UITextFieldDelegate>:

#import "MaterialDialogs.h"

Replace alert controller class

Change the class of the alert controller shown when the form isn't filled out completely.

In ViewController.m, in saveDidTouch:, replace the UIAlertController variable with an MDCAlertController:

MDCAlertController *saveAlert = [MDCAlertController alertControllerWithTitle:@"Do you accept these terms?" message:kLorem];

Replace alert action class

In ViewController.m, in saveDidTouch:, replace the UIAlertAction variables with MDCAlertActions:

[saveAlert addAction:[MDCAlertAction actionWithTitle:@"Yes" handler:^(MDCAlertAction * _Nonnull action) {
    self.name.text = nil;
    self.address.text = nil;
    self.city.text = nil;
    self.state.text = nil;
    self.zip.text = nil;
}]];
[saveAlert addAction:[MDCAlertAction actionWithTitle:@"Cancel" handler:^(MDCAlertAction * _Nonnull action) {
}]];

Import the dialogs themer

At the top of ViewController.m, add the following below #import "MaterialDialogs.h"

#import "MaterialDialogs+DialogThemer.h"

Theme the alert

In ViewController.m, add the following to saveDidTouch just before [self presentViewController:saveAlert animated:YES completion:nil];

MDCAlertScheme *alertScheme = [[MDCAlertScheme alloc] init];
  [MDCAlertControllerThemer applyScheme:alertScheme toAlertController:saveAlert];

Build and run on an iPad. Press SAVE:

c30289d17a2e74c5.png

The alert is big enough to handle the long text.

6. Recap

You've replaced some common components to show immediate value: text fields, buttons, alerts or menus and you didn't have to do a wholesale redesign of your app. Other components can make a big difference too, such as the top app bar and typography.

Next steps

You can explore even more components in MDC-iOS by visiting the MDC-iOS catalog.

I was able to complete this codelab with a reasonable amount of time and effort

Strongly agree Agree Neutral Disagree Strongly disagree

I would like to continue using Material Components in the future

Strongly agree Agree Neutral Disagree Strongly disagree