As a developer I often find myself in the situation where I have to face the problem to store and get some kind of information in the most possible handy way.
In particular, this happens when you design a static menu list (e.g side panel or harburger menu) using a UITableView.
In this article I will go to explain how to implement it using the concept of Interaction Object in order to keep our code clean and testable.
The problem
In my last two companies I used to work, I was in the situation to create a menu list like so:
As a static menu, it was not populated from any datasource, therefore I thought “what is the best way to represent the menu items?”.
A naïve and rude approach would be something like:
-(UITableViewCell*)tableView:(UITableView*)tableViewcellForRowAtIndexPath:(NSIndexPath*)indexPath{switch(indexPath.row){//....get/create a cell etc.case0:{cell.title=@"Home";break;}case1:{cell.title=@"Favourites";break;}//...}//..returncell;}-(void)tableView:(UITableView*)tableViewdidSelectRowAtIndexPath:(NSIndexPath*)indexPath{switch(indexPath.row){case0:{[selfgoToHome];break;}case1:{[selfgoToFavourites];break;}//...}//..}
……
Yes, that’s ugly and not scalable.
What if you want to add another cell later? And if you want the new cell to be somewhere in the middle? You’d have to change every case’s number. What if you want to display different cells according with a condition (i.e. user/admin)?
In short, this is not a good approach at all. So… how can we go with this?
The solution
As a menu, any cell needs 3 basic and fundamental things:
An image;
A title;
An action.
The idea is to have a component (MGInteractionObject, precisely) to store these information, and having then an array of these components in order to associate them to the cells. Moreover, this approach will give us the opportunity to manage the array, choosing which items to show and which not to, according to certain events or conditions.
Basically, as I said, we store a title for the menu item, an image and an action, which will be performed by a block, or by a selector when the user taps on the menu entry. Finally, we have an enumeration in order to present to the developer the different ways to perform the action.
For simplicity, here I reported just some basic information to store, but it could be expanded a lot further, for example with the following properties:
#import "MGInteractionObject.h"@implementationMGInteractionObject#pragma mark - Public init-(id)init{@throw[NSExceptionexceptionWithName:NSInternalInconsistencyExceptionreason:@"Must use initWithTitle:imageName:selector: or initWithTitle:imageName:performBlock:"userInfo:nil];}-(instancetype)initWithTitle:(NSString*)titleimageName:(NSString*)imageNameselector:(SEL)selector{return[selfinitWithTitle:titleimageName:imageNameselector:selectorperformBlock:nil];}-(instancetype)initWithTitle:(NSString*)titleimageName:(NSString*)imageNameperformBlock:(performBlock)block{return[selfinitWithTitle:titleimageName:imageNameselector:nilperformBlock:block];}#pragma mark - Private init-(instancetype)initWithTitle:(NSString*)titleimageName:(NSString*)imageNameselector:(SEL)selectorperformBlock:(performBlock)block{if(self=[superinit]){_title=[titlecopy];_imageName=[imageNamecopy];_selector=selector;_block=[blockcopy];_actionType=(selector)?MGActionTypeSelector:MGActionTypeBlock;}returnself;}@end
Simple as that.
Now that we have the base element, let’s proceed to use it properly.
How to use it
What we need to do now, is to create an interaction object for each menu item.
Supposing to have 4 items:
Home;
Favourites;
Stores;
Admin options.
we can simply go to create 4 interaction objects like these:
Having these 4 objects, we return an array of MGInteractionObject, according with the conditions we want to consider. In this example, it is checked if the user is an admin (user.isAdmin) to decide if add or not the admin interaction object, in the array tableButtons.
Now all we need to do yet is to reload the tableview to show the content. Here is the implementation of tableView:cellForRowAtIndexPath:
123456789101112131415161718
-(UITableViewCell*)tableView:(UITableView*)tableViewcellForRowAtIndexPath:(NSIndexPath*)indexPath{staticNSString*cellIdentifier=@"InteractionCell";MGInteractionCell*cell=[tableViewdequeueReusableCellWithIdentifier:cellIdentifier];if(!cell){cell=[[MGInteractionCellalloc]initWithStyle:UITableViewCellStyleDefaultreuseIdentifier:cellIdentifier];}//Get the interaction objectMGInteractionObject*interactionObject=_tableButtons[indexPath.row];//Set the interaction object for this cell to configure itcell.interactionObject=interactionObject;returncell;}
Here we are using a custom cell MGInteractionCell which has the property interactionObject, which through the setter method, configures the cell.
Obviously, it could be also used a normal UITableViewCell setting the title and the image directly from this method.
Finally, when a user taps on a cell, the method tableView:didSelectRowAtIndexPath: is called:
123456789101112131415161718
-(void)tableView:(UITableView*)tableViewdidSelectRowAtIndexPath:(NSIndexPath*)indexPath{//Get the interaction objectMGInteractionObject*interactionObject=_tableButtons[indexPath.row];//Perform the block or the selectorswitch(interactionObject.interactionType){caseMGActionTypeBlock:interactionObject.block();break;caseMGActionTypeSelector:{if([selfrespondsToSelector:interactionObject.selector]){[selfperformSelector:interactionObject.selectorwithObject:nil];}break;}}}
Here we check the interaction type and make a call to the block or perform the selector.