MDC-102 iOS: Material Structure and Layout (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

In codelab MDC-101, you used two Material Components (MDC) to build a login page: text fields and buttons with ink ripples. Now let's expand upon this foundation by adding navigation, structure, and data.

What you'll build

In this codelab, you'll build a home screen for an app called Shrine, an e-commerce app that sells clothing and home goods. It will contain:

  • A top app bar
  • A grid list (which contains products)

c3d2f46397ed4a78.png

MDC-iOS components in this codelab

  • Text Field
  • Button
  • Ripple

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 within the material-components-ios-codelabs-master/MDC-102/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-102/Swift/Starter/Shrine

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 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.Note: Screenshots are generated from building on an iPhone 6s.

Success! You should see the Shrine login page from the MDC-101 codelab running in your simulator.

88c2fbfe54559be4.png

Now that the login screen is looking good, let's populate it with some products.

3. Add a top app bar

The home screen is revealed when the login page is dismissed, with a screen that says "You did it!". That's great! But now our user needs to have actions that they can take, as well as a sense of where they are in the app. To help with that, let's add navigation.

Material Design offers navigation patterns that ensure a high degree of usability. One of the most visible components is the top app bar.

Let's give users access to key actions by adding a top app bar.

Add an app bar property

In HomeViewController.swift, add the following property:

//TODO: Add an appBarViewController property
var appBarViewController = MDCAppBarViewController()

Initialize and configure the app bar

While still in HomeViewController.swift, add the following to the viewDidLoad method:

// AppBarViewController Setup
//TODO: Add the appBar controller and views
self.addChildViewController(self.appBarViewController)
self.view.addSubview(self.appBarViewController.view)
self.appBarViewController.didMove(toParentViewController: self)

// Set the tracking scroll view.
self.appBarViewController.headerView.trackingScrollView = self.collectionView

Setup the scrollview delegate methods

While still in HomeViewController.swift, add the following implementation for UIScrollViewDelegate:

//MARK: - UIScrollViewDelegate

// The following four methods must be forwarded to the tracking scroll view in order to implement
// the Flexible Header's behavior.

//TODO: Send the scrollView delegate messages to our appBar's headerView
extension HomeViewController {

  override func scrollViewDidScroll(_ scrollView: UIScrollView) {
    if (scrollView == self.appBarViewController.headerView.trackingScrollView) {
      self.appBarViewController.headerView.trackingScrollDidScroll()
    }
  }

  override func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    if (scrollView == self.appBarViewController.headerView.trackingScrollView) {
      self.appBarViewController.headerView.trackingScrollDidEndDecelerating()
    }
  }

  override func scrollViewDidEndDragging(_ scrollView: UIScrollView,
                                         willDecelerate decelerate: Bool) {
    let headerView = self.appBarViewController.headerView
    if (scrollView == headerView.trackingScrollView) {
      headerView.trackingScrollDidEndDraggingWillDecelerate(decelerate)
    }
  }

  override func scrollViewWillEndDragging(_ scrollView: UIScrollView,
                                          withVelocity velocity: CGPoint,
                                          targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    let headerView = self.appBarViewController.headerView
    if (scrollView == headerView.trackingScrollView) {
      headerView.trackingScrollWillEndDragging(withVelocity: velocity,
                                               targetContentOffset: targetContentOffset)
    }
  }

}

Add navigation items

App bars display navigation items attached to their parent view controller. Let's add some leading and trailing navigation items in viewDidLoad.

// Setup Navigation Items
//TODO: Create the items and set them on the view controller's navigationItems properties
let menuItemImage = UIImage(named: "MenuItem")
let templatedMenuItemImage = menuItemImage?.withRenderingMode(.alwaysTemplate)
let menuItem = UIBarButtonItem(image: templatedMenuItemImage,
                               style: .plain,
                               target: self,
                               action: #selector(menuItemTapped(sender:)))
self.navigationItem.leftBarButtonItem = menuItem

let searchItemImage = UIImage(named: "SearchItem")
let templatedSearchItemImage = searchItemImage?.withRenderingMode(.alwaysTemplate)
let searchItem = UIBarButtonItem(image: templatedSearchItemImage,
                               style: .plain,
                               target: nil,
                               action: nil)
let tuneItemImage = UIImage(named: "TuneItem")
let templatedTuneItemImage = tuneItemImage?.withRenderingMode(.alwaysTemplate)
let tuneItem = UIBarButtonItem(image: templatedTuneItemImage,
                                 style: .plain,
                                 target: nil,
                                 action: nil)
self.navigationItem.rightBarButtonItems = [ tuneItem, searchItem ]

Build and run

You app should now have an app bar with associated navigation items. (Click Next on the login screen to see it.)

ad1a332ca951a310.png

Now the app has a leading button, a title, and two actions on the trailing side.

4. Plug our Model into the collection

Now that our app has some structure, let's connect our collection view into our catalog of products.

Add a cell

Let's start by adding a single cell underneath the top app bar. This cell should have a region for an image, title, and label (for secondary text).

To make the cell visible, in HomeViewController.swift, add the following methods:

override func collectionView(_ collectionView: UICollectionView,
                             numberOfItemsInSection section: Int) -> Int {
  //TODO: Set the number of cells to be equal to the number of products in the catalog
  return 1
}

  override func collectionView(_ collectionView: UICollectionView,
                               cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = self.collectionView?.dequeueReusableCell(withReuseIdentifier: "ProductCell",
                                                        for: indexPath) as! ProductCell
    //TODO: Set the properties of the cell to reflect to product from the model

    return cell
  }

Build and run

In this preview, you can see the basic cell. It includes two labels and a space for an image of the product.

12a98bc86a06fc5.png

Populate cells with our model data

To return the number of products in our catalog, in HomeViewController.swift, update the collectionView(numberOfItemsInSection:) delegate method.

  override func collectionView(_ collectionView: UICollectionView,
                               numberOfItemsInSection section: Int) -> Int {
    //TODO: Set the number of cells to be equal to the number of products in the catalog
    return Catalog.count
  }

Now that we have created the correct number of cells for our catalog, populate each cell with the appropriate product by updating collectionView(cellForItemAt:)

  override func collectionView(_ collectionView: UICollectionView,
                               cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = self.collectionView?.dequeueReusableCell(withReuseIdentifier: "ProductCell",
                                                        for: indexPath) as! ProductCell
    //TODO: Set the properties of the cell to reflect to product from the model
    let product = Catalog.productAtIndex(index: indexPath.row)
    cell.imageView.image = UIImage(named: product.imageName)
    cell.nameLabel.text = product.productName
    cell.priceLabel.text = product.price

    return cell
  }

Build and run

You did it! We can now scroll through the entire catalog of Shrine products.

d3912bd12065bedd.png

5. Migrate from UICollectionViewCell to MDCCardCollectionViewCell

Now that our product collection is being displayed, let's change our existing collection view cells into Material cards. MDC-iOS provides a card embedded in the UICollectionViewCell that can easily be integrated into your existing UI.

Convert the UICollectionViewCell to an MDCCardCollectionViewCell

Cards provide additional functionality and customization options, including support for ink, shadows, and borders. Let's take advantage of those built-in features.

In ProductCell.swift change the superclass from UICollectionViewCell to MDCCardCollectionCell.

class ProductCell: MDCCardCollectionCell

In ProductCell.swift, configure our new properties in the awakeFromNib() method.

override func awakeFromNib() {
  super.awakeFromNib()

  //TODO: Configure the cell properties
  self.backgroundColor = .white

  //TODO: Configure the MDCCardCollectionCell specific properties
  self.cornerRadius = 4.0;
  self.setBorderWidth(1.0, for:.normal)
  self.setBorderColor(.lightGray, for: .normal)
}

Build and run

The products are now be displayed within cards, which support an ink ripple animation. Try it out!

c3d2f46397ed4a78.png

6. Recap

Our app flow takes the user from the login screen to a home screen, where products can be viewed. In just a few lines of code, we added a top app bar (with a title and three buttons) and displayed our app content in cards. Our home screen is now simple and functional, with a basic structure and actionable content.

Next steps

With the app bar, cards, text fields, and buttons, we've now used four Material Design core components from the MDC-iOS library. Great job! You can explore even more components by visiting the MDC Catalog for iOS.

While it's fully functioning, our app doesn't yet express any particular brand or point of view. In MDC-103: Material Design Theming with Color, Shape, Elevation and Type, we'll customize the style of these components to express a vibrant, modern brand.

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