thumbnail

Back to Blog

Creating a drill-down menu with ShinobiEssentials

Posted on 28 May 2014 Written by Alison Clarke

If you’re building a store app, or any other app that has a tree-like structure, then a drill-down menu can be a great navigation tool. And the good news is ShinobiEssentials provides you with all the components you need to build one! This blog post will show you how to use the Sliding Overlay, Accordion and Flow Layout components to build a drill-down menu.

To get started, you’ll need a copy of ShinobiEssentials – if you haven’t got one yet, you can download a free trial. This tutorial will take you through the process step-by-step, but if you get stuck then you can view the finished project on GitHub, or download the zip.

We’re going to build a menu for a dummy grocery store app. The finished product will look like this:

Screenshot5

Getting started

In our example, we’ll use a Single View Application in Xcode, set up for iPad. You’ll need to add the following resources to your project:

  • QuartzCore.framework
  • Security.framework (only needed by trial users)
  • ShinobiEssentials.framework (if you’ve used the installer, this should be available under “Developer Frameworks”; otherwise you’ll need to drag-drop in ShinobiEssentials.framework from wherever you saved the files.)
  • ShinobiEssentials.bundle (drag-drop it from wherever you saved the files)

If you’re using a trial version, open up ViewController.m and add in the following line at the top of viewDidLoad, filling in your license key:

[SEssentials setLicenseKey:@"your-license-key"];

We’ll start by adding a sliding overlay to our view. This will give us a way to open up the menu when the user taps on the icon. Edit ViewController.m to add the imports, instance variables, and viewDidLoad implementation as follows:

#import "ViewController.h"
#import <ShinobiEssentials/ShinobiEssentials.h>

@implementation ViewController {
  UIView *mainView;
  UILabel *mainLabel;
  SEssentialsSlidingOverlay *slidingView;
}


- (void)viewDidLoad {
  [super viewDidLoad];
	
  // Create a sliding overlay and add it to our view
  slidingView = [[SEssentialsSlidingOverlay alloc] initWithFrame:self.view.frame andToolbar:YES];
  [self.view addSubview:slidingView];
  
  // Setup overlay (the main content area)
  mainView = [[UILabel alloc] initWithFrame:CGRectMake(0,
                                                       0,
                                                       slidingView.overlay.frame.size.width,
                                                       self.view.frame.size.height)];
  [slidingView.overlay addSubview:mainView];
  
  mainLabel = [[UILabel alloc] initWithFrame:CGRectMake(30, 30, 200, 50)];
  mainLabel.backgroundColor = [UIColor clearColor];
  mainLabel.text = @"Welcome to my store!";
  [mainView addSubview: mainLabel];
}

@end

Here we create a sliding overlay with a toolbar, and a view that will contain the main store content. For the purposes of our demo, we’re not too interested in the main content so we just add a label to it that displays some welcome text. You can run the app now to see the welcome message and a toolbar; click on the menu icon and the main content slides to the right to reveal what will eventually be our menu.

Screenshot1

Creating the menu data

Before we can add items to our menu, we need to know what the menu will be displaying. In a real-world store app you may already have a data structure you can use to extract the menu items, but for the purposes of our demo app we’re going to create a simple MenuSection class which we’ll use to represent a single section of the menu. It contains the name of the section, an array of items to display in the section (these will just be strings), and some colors to use when displaying the section. (In a real-world app you’d probably want to separate the styling from the data, but doing it this way keeps things simple for our demo.)

Create a new Objective-C class MenuSection which subclasses NSObject. Edit MenuSection.h to look like this:

@interface MenuSection : NSObject

@property (nonatomic, strong) NSString *sectionName;
@property (nonatomic, strong) NSArray *items;
@property (nonatomic, strong) UIColor *lightDisplayColor;
@property (nonatomic, strong) UIColor *darkDisplayColor;

-(instancetype)initWithSectionName:(NSString *)sectionName
                             items:(NSArray *)items
                 lightDisplayColor:(UIColor *)lightDisplayColor
                  darkDisplayColor:(UIColor *)darkDisplayColor;

@end

And edit MenuSection.m to implement the initializer:

#import "MenuSection.h"

@implementation MenuSection

-(instancetype)initWithSectionName:(NSString *)sectionName
                             items:(NSArray *)items
                 lightDisplayColor:(UIColor *)lightDisplayColor
                    darkDisplayColor:(UIColor *)darkDisplayColor {
  self = [super init];
  if (self) {
    self.sectionName = sectionName;
    self.items = items;
    self.lightDisplayColor = lightDisplayColor;
    self.darkDisplayColor = darkDisplayColor;
  }
  return self;
}

@end

Next, we’ll make use of this class inside ViewController.m. Import the new class and add an instance variable to hold the data:

#import "MenuSection.h"

@implementation ViewController {
  ...
  NSArray *data;
}

Then create a new method which creates an array of MenuSection objects, populated with some dummy data. (Our grocery store is somewhat limited in stock.)

- (void)setupData {
  // Set up menu data
  data = @[[[MenuSection alloc] initWithSectionName:@"Fruit"
                                                items:@[@"Apples",@"Oranges",@"Bananas",@"Pears"]
                                    lightDisplayColor:[UIColor colorWithRed:0.99 green:0.90 blue:0.80 alpha:1.0]
                                     darkDisplayColor:[UIColor colorWithRed:0.96 green:0.70 blue:0.42 alpha:1.0]],
          [[MenuSection alloc] initWithSectionName:@"Vegetables"
                                               items:@[@"Potatoes",@"Carrots",@"Broccoli"]
                                   lightDisplayColor:[UIColor colorWithRed:0.85 green:0.92 blue:0.83 alpha:1.0]
                                    darkDisplayColor:[UIColor colorWithRed:0.58 green:0.77 blue:0.49 alpha:1.0]],
          [[MenuSection alloc] initWithSectionName:@"Candy"
                                               items:@[@"Chocolate bars",@"Chewy candy",@"Hard candy",@"Lollipops",@"Gum"]
                                   lightDisplayColor:[UIColor colorWithRed:0.92 green:0.82 blue:0.86 alpha:1.0]
                                    darkDisplayColor:[UIColor colorWithRed:0.76 green:0.48 blue:0.63 alpha:1.0]]];
}

Finally call the setup method from viewDidLoad:

- (void)viewDidLoad {
  ...
  
  // Set up the data
  [self setupData];  
}

Adding an accordion

So we’ve got some menu data to display, and a blank space to put it in. Let’s add in an SEssentialsAccordion as the basis of our menu: each of our MenuSection objects will be displayed as an accordion section, and when a section is opened it will list the MenuSection‘s items.

We’ll need two new instance variables: one to store the accordion, and another to store a map from accordion sections to the views to display in those sections:

@implementation ViewController {
  ...
  SEssentialsAccordion *accordion;
  NSMutableDictionary *mapSectionToView;
}

We’re going to make our view controller the data source for our accordion, so edit ViewController.h to import ShinobiEssentials and declare the protocol:

#import <ShinobiEssentials/ShinobiEssentials.h>

@interface ViewController : UIViewController<SEssentialsAccordionDataSource>

@end

Now add the following code to viewDidLoad to create our menu accordion:

  ...
  // Set up underlay: an accordion which will display the menu
  accordion = [[SEssentialsAccordion alloc] initWithFrame:CGRectInset(slidingView.underlay.bounds, 0, 50)];
  accordion.dataSource = self;
  accordion.cornerRadius = 0;
  accordion.editable = NO;
  
  // Set up accordion sections
  mapSectionToView = [[NSMutableDictionary alloc] init];
  
  for(MenuSection *section in data) {
    [self addSectionToMenu:section];
  }
  
  // Add the accordion to the underlay
  [slidingView.underlay addSubview:accordion];

This code first creates an accordion to fit inside the sliding view’s underlay, sets its data source to be ourself, and tweaks a couple of its settings. Next, it initialises the dictionary which will hold our accordion sections, then loops through all of the MenuSection objects in our data array, and creates a new menu section for each (we’ll look at the implementation of addSectionToMenu: below). Finally, it adds the accordion to the sliding view’s underlay.

Our initial implementation of addSectionToMenu: looks like this:

- (void)addSectionToMenu:(MenuSection *)menuSection {
  // Create an accordion section
  SEssentialsAccordionSection *section = [[SEssentialsAccordionSection alloc]
                                          initWithFrame:CGRectMake(0, 0, accordion.bounds.size.width, 30)
                                          andTitle:menuSection.sectionName];
  
  // Create its content
  UIView *sectionContent = [[UIView alloc] initWithFrame:CGRectMake(0, 0, accordion.bounds.size.width, 100)];
  
  // Associate the section content with the accordion section
  // Note we wrap the section in an NSValue which implements NSCopying
  [mapSectionToView setObject:sectionContent forKey:[NSValue valueWithNonretainedObject:section]];
  
  // Add the section to the accordion
  [accordion addSection:section];
}

The method first creates an accordion section, using the sectionName of the MenuSection as its title, then creates an empty UIView (for now) to use as the content. It then stores the content in our mapSectionToView dictionary, and adds the section to the accordion – all fairly standard accordion usage.

The final step to get the accordion working is to implement the data source method accordion:contentForSection:, to provide the content for the each accordion section (fairly standard stuff again):

- (UIView *)accordion:(SEssentialsAccordion *)accordion
    contentForSection:(SEssentialsAccordionSection *)section {
  // Look the section up in our dictionary to find the associated content
  // Note that the key has to be wrapped as an NSValue for use in a NSDictionary
  return [mapSectionToView objectForKey:[NSValue valueWithNonretainedObject:section]];
}

If you run the app again now, you’ll be able to see the accordion in action, though the sections are still empty.

Screenshot2

Using the flow layout

We now need to add each section’s items to our menu. There are various ways we could achieve this, e.g. using a UITableView, or manually positioning subviews, but we’re going to use the flow layout provided by ShinobiEssentials, because it makes it really easy to lay out the subviews. (The flow layout also gives you more flexibility to change the menu later on, e.g. you could reorder menu items in response to the user clicking on a “sort” button, though we won’t be doing that as part of this tutorial.)

Add the following method to viewDidLoad:

- (UIView *)createSectionContent:(MenuSection *)menuSection {
  // Create a flow layout to add to the accordion
  SEssentialsFlowLayout *sectionContent =
  [[SEssentialsFlowLayout alloc] initWithFrame:CGRectMake(0,
                                                          0,
                                                          accordion.bounds.size.width,
                                                          [menuSection.items count]*30)];
  // Tweak the settings to remove padding and spacing
  sectionContent.editable = NO;
  sectionContent.horizontalPadding = 0;
  sectionContent.verticalPadding = 0;
  sectionContent.horizontalSubviewSpacing = 0;
  sectionContent.verticalSubviewSpacing = 0;
  
  // Add a new row for each item in the menu section
  for(NSString *item in menuSection.items) {
    UILabel *row = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, accordion.bounds.size.width, 30)];
    row.backgroundColor = [UIColor clearColor];
    row.textAlignment = NSTextAlignmentCenter;
    row.text = item;
    // Add the row to the flow layout
    [sectionContent addManagedSubview:row];
  }
  
  return sectionContent;
}

We first create a flow layout, with frame height 30px times the number of items to display. We then change some of the settings, so it’s not editable and so there’s no padding between or around the items. Next, we loop through the items and create a UILabel (with height 30px) for each, adding it to the flow layout.

We then call this method inside addSectionToMenu, instead of creating an empty view:

  ...  
  // Create its content
  UIView *sectionContent = [self createSectionContent:menuSection];

If you run the app at this point you’ll see that the menu sections are now populated with the items:

Screenshot3

That all looks very nice, but surely the point of a menu is that you tap it and it shows you something new? Let’s get things working a bit better…

Responding to taps

As a first stab at responding to user taps, we’ll just add a gesture recognizer to each item’s label. Edit createSectionContent: as follows:

  // Add a new row for each item in the menu section
  for(NSString *item in menuSection.items) {
    ...
    
    // Add a tap gesture recognizer
    UITapGestureRecognizer *tapRecog = [[UITapGestureRecognizer alloc]
                                        initWithTarget:self
                                        action:@selector(menuItemTapped:)];
    [row addGestureRecognizer:tapRecog];
    
    // Add the row to the flow layout
    [sectionContent addManagedSubview:row];
  }

Then add a method menuItemTapped:, which just changes the text on the overlay:

- (void)menuItemTapped:(UITapGestureRecognizer*)recog {
  // Set the text and background color on the overlay
  mainLabel.text = ((UILabel*)recog.view).text;
}

Running the app now, you’ll see that this works as expected, but it would be nice if the menu indicated which item we’ve selected, by changing its background color. If you cast your mind back to when we created the data, you may remember that each section has some display colors we could use for this purpose…but as we can’t pass a color using the gesture recognizer, we’ll need each label to somehow know what color it should be when it’s selected. So let’s create a wrapper class for each item. We’ll call the class MenuItemRow, and it will subclass UIView.

Add the following to MenuItemRow.h:

#import "MenuSection.h"

@interface MenuItemRow : UIView

@property (nonatomic, weak) NSString *text;
@property (nonatomic, strong) MenuSection *section;

- (void)select;
- (void)deselect;

@end

So our rows have properties for the text to display and for their parent MenuSection, and methods to call when the row is selected or deselected. The implementation in MenuItemRow.m looks like this:

#import "MenuItemRow.h"

@interface MenuItemRow () {
  UILabel *textLabel;
}

@end

@implementation MenuItemRow

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
      // Create a label to display the relevant text
      textLabel = [[UILabel alloc] initWithFrame:CGRectMake(10,
                                                            0,
                                                            self.bounds.size.width-20,
                                                            self.bounds.size.height)];
      textLabel.backgroundColor = [UIColor clearColor];
      textLabel.textAlignment = NSTextAlignmentCenter;
      [self addSubview:textLabel];
    }
    return self;
}

- (void)select {
  self.backgroundColor = self.section.lightDisplayColor;
}

- (void)deselect {
  self.backgroundColor = [UIColor clearColor];
}


#pragma mark - Property overrides
- (void)setText:(NSString *)text {
  textLabel.text = text;
}

- (NSString *)text {
  return textLabel.text;
}

@end

The constructor just creates a UILabel, as we previously did inside createSectionContent:. The selected method sets the background color to the section’s light display color property, and the deselected method sets it back to clear. Finally the property overrides for text set and get the the text on the label.

Back in ViewController.m, import the new class and edit the loop in createSectionContent: as follows:

  // Add a new row for each item in the menu section
  for(NSString *item in menuSection.items) {
    MenuItemRow *row = [[MenuItemRow alloc] initWithFrame:CGRectMake(0, 0, accordion.bounds.size.width, 30)];
    row.text = item;
    row.section = menuSection;
    
    // Add a tap gesture recognizer
    UITapGestureRecognizer *tapRecog = [[UITapGestureRecognizer alloc]
                                        initWithTarget:self
                                        action:@selector(menuItemTapped:)];
    [tapRecog addTarget:row action:@selector(select)];
    [row addGestureRecognizer:tapRecog];
    
    // Add the row to the flow layout
    [sectionContent addManagedSubview:row];
  }

We’re now creating an instance of MenuItemRow for each item. Note that we also add a target to the gesture recognizer to call the selected method on the selected row.

We also need to deselect the currently selected item, so we’ll need to keep track of it in a new instance variable:

@implementation ViewController {
  ...
  MenuItemRow *selectedItem;
}

Now edit menuItemTapped: to use our new MenuItemRow:

- (void)menuItemTapped:(UITapGestureRecognizer*)recog {
  // Unhighlight currently selected label
  [selectedItem deselect];
  
  // Set our reference to the selected label
  selectedItem = (MenuItemRow*)recog.view;
  // Highlight it
  [selectedItem select];
  // Set the text and background color on the overlay
  mainLabel.text = selectedItem.text;
  mainView.backgroundColor = selectedItem.section.lightDisplayColor;
}

So we now deselect the old row, select the new one, and change both the text and the background color on the overlay.

The app now highlights the currently selected item, and changes the overlay’s background color to match:

Screenshot4

Finishing it off

We’re nearly there! But the menu section headings don’t look quite right yet. Let’s make the text a bit bigger and change the background color. To do that we’ll need to alter our addSectionToMenu: method to create a custom header:

- (void)addSectionToMenu:(MenuSection *)menuSection {
  // Create a style for the section header
  SEssentialsAccordionSectionHeaderStyle *headerStyle = [[SEssentialsAccordionSectionHeaderStyle alloc]
                                                         initWithTheme:[SEssentials theme]];
  headerStyle.backgroundColor = menuSection.darkDisplayColor;
  headerStyle.selectedBackgroundColor = menuSection.darkDisplayColor;
  headerStyle.font = [UIFont systemFontOfSize:18.f];
  
  // Create the section header
  SEssentialsAccordionSectionHeader *sectionHeader =
  [[SEssentialsAccordionSectionHeader alloc] initWithFrame:CGRectMake(0, 0, accordion.bounds.size.width, 40)
                                                  andTitle:menuSection.sectionName
                                                  andStyle:headerStyle];
  
  // Create an accordion section
  SEssentialsAccordionSection *section = [[SEssentialsAccordionSection alloc]
                                          initWithFrame:CGRectMake(0, 0, accordion.bounds.size.width, 30)
                                          andHeader:sectionHeader];
  
  ...
}

The first thing we do is to create a style for the menu, and use the darkDisplayColor from the MenuSection as its background color (used when the section is closed) and selected background color (used when the section is open). (Have a play around with these colors: you might like to use the light color for one of these properties).

We then create the section header, passing it the header style. Finally we change the initializer used to create the accordion section to initWithFrame:andHeader:, so we can pass it the section header we’ve just created.

Running the app now, you’ll see the menu sections are now styled as we want:

Screenshot5

So there we have it: a drill-down menu created using ShinobiEssentials. Of course, ShinobiEssentials allows you to customise much more than I’ve shown in this tutorial – head over to the docs to find out more. And feel free to fork the GitHub project to have a play around with it – let us know how you get on!

Back to Blog