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

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/Swift/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/Swift/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.

5f99452a03378a0d.png

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

fc699583cc97c6f4.png

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.

c0699d9dbf2b7a55.png

Success! You should see the app and its form.

56a49673c069283f.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 it 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 Class field, type MDCTextField or select it from the dropdown menu. Do this for all 5 text fields.

e08e1df90428c103.png

Set the text fields' background color to white

In order for the text field to have the elegant effect of the placeholder label cutting through the border, we need to set its background color to white.

In the storyboard, select all the text fields and open the Attributes Inspector. (You can also open the Attributes Inspector in View > Utilities > Show Attributes Inspector.) In the View section, change Background to be White Color. (You may need to scroll down to find this section.)

954ac203a8c167b9.png

Import MDC in the view controller

At the top of ViewController.swift, add the following under import UIKit:

import MaterialComponents

Change the text field class in the view controller

In ViewController.swift, change each UITextField to MDCTextField.

@IBOutlet weak var name: MDCTextField!
@IBOutlet weak var address: MDCTextField!
@IBOutlet weak var city: MDCTextField!
@IBOutlet weak var state: MDCTextField!
@IBOutlet weak var zip: MDCTextField!

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.swift, add the following under @IBOutlet weak var saveButton: MDCRaisedButton!:

// MARK: Properties

var nameController: MDCTextInputControllerOutlined?
var addressController: MDCTextInputControllerOutlined?
var cityController: MDCTextInputControllerOutlined?
var stateController: MDCTextInputControllerOutlined?
var zipController: MDCTextInputControllerOutlined?

Now we initialize them in viewDidLoad.

In ViewController.swift, add the following to viewDidLoad under super.viewDidLoad():

// TODO: Instantiate controllers

nameController = MDCTextInputControllerOutlined(textInput: name)
addressController = MDCTextInputControllerOutlined(textInput: address)
cityController = MDCTextInputControllerOutlined(textInput: city)
stateController = MDCTextInputControllerOutlined(textInput: state)
zipController = MDCTextInputControllerOutlined(textInput: zip)

Build and run. Click on the Name field to see its colored highlight.

526618a67374f24b.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 Main.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:

8afa0dc2fab01062.png

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

741d7ab7d74f06ef.png

Build and run. Notice that the side margins are narrower.

aa47a6e406364af6.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.swift, add the following at the end of viewDidLoad:

zip.delegate = self

Also in ViewController.swift, add the following method under viewDidLoad:

// MARK: UITextFieldDelegate methods

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
  guard let text = textField.text,
  let range = Range(range, in: text),
  textField == zip else {
    return true
  }

  let finishedString = text.replacingCharacters(in: range, with: string)
  if finishedString.rangeOfCharacter(from: CharacterSet.letters) != nil {
    zipController?.setErrorText("Error: Zip can only contain numbers", errorAccessibilityValue: nil)
  } else {
    zipController?.setErrorText(nil, errorAccessibilityValue: nil)
  }

  return true
}

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

933110b2b7a9ce7b.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.

Change button class

In ViewController.swift, in the properties section, change the class of the @IBOutlet weak var saveButton: UIButton! to MDCRaisedButton:

@IBOutlet weak var saveButton: MDCButton!

In Main.storyboard, select the save button and open the Identity 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, select MDCButton from the dropdown menu. (Notice all other classes you could choose!):

6bca9e9e78a2a619.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.swift, add the following below @IBOutlet weak var saveButton: MDCButton! let buttonScheme = MDCButtonScheme()

let buttonScheme = MDCButtonScheme()

Theme the button

In ViewController.swift, add the following to viewDidLoad under super.viewDidLoad()

MDCContainedButtonThemer.applyScheme(buttonScheme, to: saveButton)

Make the button text uppe****rcase

Material Design recommends you make the button text uppercase.

In ViewController.swift, add the following to viewDidLoad under super.viewDidLoad()

saveButton.setTitle("Save", for: .normal)

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 Add 1 Constraint:

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:

6463e44d050e5c7.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. We'll enlarge the tappable area by adding hit area insets to the button.

Add hit area insets

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

override func viewDidLayoutSubviews() {
  super.viewDidLayoutSubviews()

  // TODO: Update `hitAreaInsets`.
  let verticalInset = min(0, (self.saveButton.bounds.height - 48) / 2)  
saveButton.hitAreaInsets = UIEdgeInsets(top: verticalInset,
                                          left: 0,
                                          bottom:verticalInset,
                                          right: 0)
}

Build and run and tap the save button. There's no warning in the console! 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 the UIAlertController is limited in the amount of text it can show on an iPad.

4509f505799a2d96.png

Replace alert controller class

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

In ViewController.swift, in saveDidTouch:, replace the UIAlertController variable with an MDCAlertController. (Notice MDCAlertController's init has fewer parameters):

let alert = MDCAlertController(title: "Do you accept these terms?", message: kLorem)

Replace alert action class

In ViewController.swift, in saveDidTouch:, replace the UIAlertAction variables with MDCAlertActions (Notice MDCAlertAction's init has fewer parameters):

alert.addAction(MDCAlertAction(title: "Yes", handler: { (_) in
  self.name.text = nil
  self.address.text = nil
  self.city.text = nil
  self.state.text = nil
  self.zip.text = nil
}))
alert.addAction(MDCAlertAction(title: "Cancel", handler: nil))

Theme the alert

In ViewController.swift, add the following to saveDidTouch just before present(alert, animated: true, completion: nil)

MDCAlertControllerThemer.applyScheme(MDCAlertScheme(), to: alert)

Switch the simulator target to an iPad, then build and run. Press SAVE:

3fa539f27e4dc805.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 whole 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