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

What are Material Design and Material Components for iOS?

Material Design is a system for building bold and beautiful digital products. By uniting style, branding, interaction, and motion under a consistent set of principles and components, product teams can realize their greatest design potential.

Material Components for iOS (MDC iOS) unites design and engineering with a library of components for creating consistency across apps and websites. As the Material Design system evolves, these components are updated to ensure consistent pixel-perfect implementation and adherence to Google's front-end development standards. MDC is also available for Android, the web, and Flutter.

In this codelab, you'll build a login page using several MDC iOS components.

What you'll build

This codelab is the first of four codelabs that will guide you through building an app called Shrine, an e-commerce website that sells clothing and home goods. It will demonstrate how you can customize components to reflect any brand or style using MDC-iOS.

In this codelab, you'll build a login page for Shrine that contains:

MDC iOS components in this codelab

What you'll need

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

Novice Intermediate Proficient

Download the starter codelab app

Download starter app

The starter app is located within the material-components-ios-codelabs-master/MDC-101/Swift/Starter directory. If using the terminal, 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-101/Swift/Starter

Run the starter app

1. In Xcode, open the workspace "Shrine.xcworkspace".

If you see the "Welcome to Xcode" window, click "Open another project...", navigate to the file, and then click "Open."

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

OR: At the terminal enter:
open Shrine.xcworkspace

2. Run the app by pressing the play button at the top of the window.

Success! The starter code for Shrine's login screen should be running in your simulator. You should see the name "Shrine" and the Shrine logo just below it.

To begin, we'll add two text fields to our login page, where a user will be able to enter their username and password. We'll use the MDC Text Field component, which displays a floating text label by default.

Add for text field properties

In LoginViewController.swift, add the following properties underneath the existing titleLabel: UILabel property:

  //TODO: Add text fields
  let usernameTextField: MDCTextField = {
    let usernameTextField = MDCTextField()
    usernameTextField.translatesAutoresizingMaskIntoConstraints = false
    usernameTextField.clearButtonMode = .unlessEditing
    usernameTextField.backgroundColor = .white
    return usernameTextField
  }()
  let passwordTextField: MDCTextField = {
    let passwordTextField = MDCTextField()
    passwordTextField.translatesAutoresizingMaskIntoConstraints = false
    passwordTextField.isSecureTextEntry = true
    passwordTextField.backgroundColor = .white
    return passwordTextField
  }()

Then, add a Text Input Controller for each text field.

  //TODO: Add text field controllers
  let usernameTextFieldController: MDCTextInputControllerOutlined
  let passwordTextFieldController: MDCTextInputControllerOutlined

Initialize the text fields and add them to the view

In LoginViewController.swift, add the following code underneath scrollView.addSubview(logoImageView) in the viewDidLoad method.

// TextFields
//TODO: Add text fields to scroll view and setup initial state
    scrollView.addSubview(usernameTextField)
    scrollView.addSubview(passwordTextField)
    usernameTextFieldController.placeholderText = "Username"
    usernameTextField.delegate = self
    passwordTextFieldController.placeholderText = "Password"
    passwordTextField.delegate = self
    registerKeyboardNotifications()

In LoginViewController.swift, replace the existing init(nibName, nibBundleOrNil) method implementation with the one below. (This replaces the existing call to super.init).

    //TODO: Setup text field controllers
    usernameTextFieldController = MDCTextInputControllerOutlined(textInput: usernameTextField)
    passwordTextFieldController = MDCTextInputControllerOutlined(textInput: passwordTextField)
    super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)

Position the text fields in the layout

Let's position the Text Fields by adding constraints. In LoginViewController.swift, within the viewDidLoad() function, add this code underneath logoImageView's constraints:

    // Text Fields
    //TODO: Setup text field constraints
    constraints.append(NSLayoutConstraint(item: usernameTextField,
                                          attribute: .top,
                                          relatedBy: .equal,
                                          toItem: titleLabel,
                                          attribute: .bottom,
                                          multiplier: 1,
                                          constant: 22))
    constraints.append(NSLayoutConstraint(item: usernameTextField,
                                          attribute: .centerX,
                                          relatedBy: .equal,
                                          toItem: scrollView,
                                          attribute: .centerX,
                                          multiplier: 1,
                                          constant: 0))
    constraints.append(contentsOf:
      NSLayoutConstraint.constraints(withVisualFormat: "H:|-[username]-|",
                                     options: [],
                                     metrics: nil,
                                     views: [ "username" : usernameTextField]))
    constraints.append(NSLayoutConstraint(item: passwordTextField,
                                          attribute: .top,
                                          relatedBy: .equal,
                                          toItem: usernameTextField,
                                          attribute: .bottom,
                                          multiplier: 1,
                                          constant: 8))
    constraints.append(NSLayoutConstraint(item: passwordTextField,
                                          attribute: .centerX,
                                          relatedBy: .equal,
                                          toItem: scrollView,
                                          attribute: .centerX,
                                          multiplier: 1,
                                          constant: 0))
    constraints.append(contentsOf:
      NSLayoutConstraint.constraints(withVisualFormat: "H:|-[password]-|",
                                     options: [],
                                     metrics: nil,
                                     views: [ "password" : passwordTextField]))

Check for a minimum password length

Text fields can indicate if the field input is valid, or if it contains an error.

You should:

In LoginViewController.swift, add the following implementation of textFieldShouldReturn()to the UITextFieldDelegate extension.

  //TODO: Add basic password field validation in the textFieldShouldReturn delegate function
  func textFieldShouldReturn(_ textField: UITextField) -> Bool {
    textField.resignFirstResponder();

    // TextField
    if (textField == passwordTextField &&
      passwordTextField.text != nil &&
      passwordTextField.text!.count < 8) {
      passwordTextFieldController.setErrorText("Password is too short",
                                               errorAccessibilityValue: nil)
    }

    return false
  }

Build and run

The interface should now show two text fields for Username and Password! Check out the floating label animation when you click on a text field:

Next, we'll add two buttons to our login page: "Cancel" and "Next." We'll use the MDC Button component to finish building our interface.

Create properties for the buttons

In LoginViewController.swift, above the init method, add the following properties underneath the text field properties created earlier:

  // Buttons
  //TODO: Add buttons
  let cancelButton: MDCFlatButton = {
    let cancelButton = MDCFlatButton()
    cancelButton.translatesAutoresizingMaskIntoConstraints = false
    cancelButton.setTitle("CANCEL", for: .normal)
    cancelButton.addTarget(self, action: #selector(didTapCancel(sender:)), for: .touchUpInside)
    return cancelButton
  }()
  let nextButton: MDCRaisedButton = {
    let nextButton = MDCRaisedButton()
    nextButton.translatesAutoresizingMaskIntoConstraints = false
    nextButton.setTitle("NEXT", for: .normal)
    nextButton.addTarget(self, action: #selector(didTapNext(sender:)), for: .touchUpInside)
    return nextButton
  }()

Initialize the buttons and add them to the view

In LoginViewController.swift, add the following code underneath the text fields in the viewDidLoad() method:

    // Buttons
    //TODO: Add buttons to the scroll view
    scrollView.addSubview(nextButton)
    scrollView.addSubview(cancelButton)

Add layout constraints to position the buttons

To position the buttons, in viewDidLoad(), add the following code underneath the text field constraints:

    // Buttons
    //TODO: Setup button constraints
    constraints.append(NSLayoutConstraint(item: cancelButton,
                                          attribute: .top,
                                          relatedBy: .equal,
                                          toItem: passwordTextField,
                                          attribute: .bottom,
                                          multiplier: 1,
                                          constant: 8))
    constraints.append(NSLayoutConstraint(item: cancelButton,
                                          attribute: .centerY,
                                          relatedBy: .equal,
                                          toItem: nextButton,
                                          attribute: .centerY,
                                          multiplier: 1,
                                          constant: 0))
    constraints.append(contentsOf:
      NSLayoutConstraint.constraints(withVisualFormat: "H:[cancel]-[next]-|",
                                     options: NSLayoutFormatOptions(rawValue: 0),
                                     metrics: nil,
                                     views: [ "cancel" : cancelButton, "next" : nextButton]))
    constraints.append(NSLayoutConstraint(item: nextButton,
                                          attribute: .bottom,
                                          relatedBy: .equal,
                                          toItem: scrollView.contentLayoutGuide,
                                          attribute: .bottomMargin,
                                          multiplier: 1,
                                          constant: -20))

Add button actions

Add the button action handlers. (This will resolve the errors from earlier.)

  // MARK: - Action Handling
  //TODO: Add the button handlers
  @objc func didTapNext(sender: Any) {
    self.dismiss(animated: true, completion: nil)
  }

  @objc func didTapCancel(sender: Any) {
    self.dismiss(animated: true, completion: nil)
  }

Build and run

You should now see a page that contains these two buttons. Try it out!

Material Components for iOS have helped you create a beautiful login page that conforms to the Material Design guidelines, which looks and behaves consistently across all devices.

Next steps

Text Field and Button are two core components in MDC iOS, but there are many more! You can explore the rest of the components in MDC iOS.

Alternatively, head over to MDC-102: Material Design Structure and Layout to learn about AppBar and Card Collection. Thanks for trying Material Components. We hope you enjoyed this codelab!

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