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

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)

cbf64e1b0b0ae35a.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/ObjectiveC/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/ObjectiveC/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.m, under @interface HomeViewController, add the following:

// AppBarViewController Property
//TODO: Add AppBarViewController Property
@property(nonatomic, strong) MDCAppBarViewController *appBarViewController;

Initialize and configure the app bar

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

// AppBarViewController Init
//TODO: Instantiate and add the AppBarViewController
self.appBarViewController = [[MDCAppBarViewController alloc] init];
[self addChildViewController:self.appBarViewController];
[self.view addSubview:self.appBarViewController.view];
[self.appBarViewController didMoveToParentViewController:self];

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

Setup the scrollview delegate methods

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

// TODO: Add scrollview delegate methods to forward scrolling messages
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
  if (scrollView == self.appBarViewController.headerView.trackingScrollView) {
    [self.appBarViewController.headerView trackingScrollViewDidScroll];
  }
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
  if (scrollView == self.appBarViewController.headerView.trackingScrollView) {
    [self.appBarViewController.headerView trackingScrollViewDidEndDecelerating];
  }
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
  MDCFlexibleHeaderView *headerView = self.appBarViewController.headerView;
  if (scrollView == headerView.trackingScrollView) {
    [headerView trackingScrollViewDidEndDraggingWillDecelerate:decelerate];
  }
}

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView
                     withVelocity:(CGPoint)velocity
              targetContentOffset:(inout CGPoint *)targetContentOffset {
  MDCFlexibleHeaderView *headerView = self.appBarViewController.headerView;
  if (scrollView == headerView.trackingScrollView) {
    [headerView trackingScrollViewWillEndDraggingWithVelocity: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.

// Setup Navigation Items
//TODO: Add navigation items
UIImage *menuItemImage = [UIImage imageNamed:@"MenuItem"];
UIImage *templatedMenuItemImage = [menuItemImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
UIBarButtonItem *menuItem =
[[UIBarButtonItem alloc] initWithImage:templatedMenuItemImage
                                 style:UIBarButtonItemStylePlain
                                target:self
                                action:@selector(menuItemTapped:)];
self.navigationItem.leftBarButtonItem = menuItem;

UIImage *searchItemImage = [UIImage imageNamed:@"SearchItem"];
UIImage *templateSearchItemImage = [searchItemImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
UIBarButtonItem *searchItem =
[[UIBarButtonItem alloc] initWithImage:templateSearchItemImage
                                 style:UIBarButtonItemStylePlain
                                target:nil
                                action:nil];

UIImage *tuneItemImage = [UIImage imageNamed:@"TuneItem"];
UIImage *templateTuneItemImage = [tuneItemImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
UIBarButtonItem *tuneItem =
[[UIBarButtonItem alloc] initWithImage:templateTuneItemImage
                                 style:UIBarButtonItemStylePlain
                                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.)

31d0b909acb42154.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.

Hide "You did it" text

Let's go ahead and hide the placeholder text because we are adding our own content to the home view controller.

  //TODO: Hide the "You did it!" text
  doneLabel.hidden = YES;

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.m, add the following method,:

#pragma mark - UICollectionViewDataSource

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
  //TODO: Update the following line to return the number of items in the catalog instead of 0
  return 1;
}

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.

830e4218ef70589b.png

Populate cells with our model data

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

#pragma mark - UICollectionViewDataSource

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
  // Update the following line to return the number of 
  // products in our model instead of 1
  return [Catalog productCatalog].count;
}

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

- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
  ProductCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:@"ProductCell" forIndexPath:indexPath];

  //TODO: Set the properties of the cell to reflect the product from the model
  Product *product = [[Catalog productCatalog] productAtIndex:indexPath.row];
  UIImage *productImage = [UIImage imageNamed:product.imageName];
  cell.imageView.image = productImage;
  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.

a69bb7f3dddd2e80.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.h change the superclass from UICollectionViewCell to MDCCardCollectionCell.

//TODO: Change the superclass from UICollectionViewCell to MDCCardCollectionCell
@interface ProductCell : MDCCardCollectionCell

In ProductCell.m, configure our new properties in the initWithCoder: method.

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
  self = [super initWithCoder:aDecoder];
  if (self) {
    //TODO: Configure the cell properties
    self.backgroundColor = [UIColor whiteColor];

    //TODO: Add MDCCardCollectionCell specific properties
    self.cornerRadius = 8.0;
    [self setBorderWidth:1.0 forState:MDCCardCellStateNormal];
    [self setBorderColor:[UIColor lightGrayColor] forState:MDCCardCellStateNormal];
  }
  return self;
}

Build and run

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

cbf64e1b0b0ae35a.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