Creating a Netflix style iOS Grid

Stephen Zaharuk / Tuesday, November 13, 2012

If you've ever used the iOS Netflix app, you've seen their opening screen. It's a list of sections and each section has a horizontal scrolling list of movies and tv shows. In my opinion this is a really neat UI, so today i'm going to show you how you can easily get the same UI by using the IGGridView control. 

Before we get started, make sure you have a copy of our NucliOS product installed. If you don't currently have a license, you can still grab our free trial

Once you've got it installed we can get started!

As always, the first step is to include the IG.framework reference. And don't forget to also include a reference to the QuartzCore.framework. 

If you just want to get the project, you can grab it here.

Before we jump into the code, let me explain what we're going to do first. Step 1 is to create a vertical scrolling grid with exactly one column. In that grid we'll have our data broken out into sections, with each section having exactly one row. Each row will contain another grid, however this grid will only scroll horizontally.  

That description might sound a bit complex, but trust me, it's actually going to be really easy. 

Step 2 is to gather data to display. In this sample, i will be pulling data from Netflix using their oData service, however since this is not the main focus of the article, I won't be describing the retrieval of the data. However, you can take a look at the attached sample project to see for yourself. 

However, while the retrieval of the data isn't important, the structure of the data is. The data needs to match the structure of the application i described above, and in this case, it needs to be hierarchical. Basically we should have a list of objects that contain a CategoryName, a float property to store the current scroll postion,  and an array of "Media". The  Media objects should have enough information for display, in this case we really just need an image url and maybe a title. 

So lets first look at our data model. Here is what the root level object will look like:

@interface NetflixData : NSObject
   @property (nonatomic, retain)NSString* category;
   @property (nonatomic, retain)NSMutableArray* media;
   @property (nonatomic, assign)CGFloat scrollPosition;
@end

And here's what our child objects will look like: 

@interface NetflixMedia: NSObject
   @property (nonatomic, retain)NSURL* imgUrl;
   @property (nonatomic, retain)NSString* title;
@end

Now that we've gotten the boring data part out of the way, lets jump into the fun part, the UI! To accomplish the Netflix UI, we'll need a total of 3 classes. 

Our ViewController:

@interface ViewController : UIViewController
{
   IGGridView* _gridView;
   IGGridViewDataSourceHelper* _ds;
}
@end

Our Custom GridView Column Definition:

@interface MediaColumnDefinition : IGGridViewColumnDefinition
@end

And a Custom GridView Cell

@interface MediaCell : IGGridViewCell
{
   IGGridView* _gridView;       IGGridViewSingleRowSingleFieldDataSourceHelper* _ds;
}
   @property(nonatomic, retain)NetflixData* data;
@end

We need a custom cell, b/c we want to have a cell with a GridView, and we create a custom column definition b/c we want to take full advantage of the built in DataSourceHelpers of the grid. 

So, you might be thinking now, "3 classes?? i though this guy said it was going to be easy".  Well don't worry, once you see the how much code you're actually going to have to write, you'll see how easy it really is. 

Lets first look at the implementation of the ViewController:

@implementation ViewController
- (void)viewDidLoad
{
   [super viewDidLoad];
   _gridView = [[IGGridView alloc]initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
   _gridView.autoresizingMask = UIViewAutoresizingFlexibleHeight |UIViewAutoresizingFlexibleWidth;
   [self.view addSubview:_gridView];
 
   _gridView.rowHeight = 200;
   _gridView.headerHeight = 0;
   _gridView.backgroundColor = [UIColor blackColor];
 
   _ds = [[IGGridViewDataSourceHelper alloc]init];    
   _ds.autoGenerateColumns = NO;
 
   [_ds.columnDefinitions addObject:[[MediaColumnDefinition alloc]initWithKey:@"media"]];
   _ds.groupingKey = @"category";
   _ds.data = [NetflixData generateData];
   _gridView.dataSource = _ds;
}
@end

Thats our entire ViewController implementation.  Most of it is just creating the gridView and putting in in the ViewController. The rest is simply creating a datasource. Important things to note: we want to make sure we disable the auto generation of columns. Then we're going insert our custom MediaColumnDefinition that we defined previously. And finally we want to make sure we set a groupingKey, which will turn on sections for the gridView.  The data thats being put into the datasourceHelper is simply an array of our NextflixData objects, that we defined above.

So thats one class. But we have 2 more to go, and i bet you're thinking "well i bet its going to get really complex really soon". But have no fear, we're almost done. 

Lets take a look at the column implementation:

@implementation MediaColumnDefinition
-(IGGridViewCell *)gridView:(IGGridView *)gridView createCell:(IGCellPath *)path usingDataSource:(IGGridViewDataSourceHelper *)dataSource
{
   NetflixData* data = [dataSource resolveDataObjectForRow:path];  
   MediaCell* cell = [gridView dequeueReusableCellWithIdentifier:@"MEDIA_CELL"];
 
   if(cell == nil)
       cell = [[MediaCell alloc]initWithReuseIdentifier:@"MEDIA_CELL"];
 
   cell.data = data; return cell;
}
@end

Thats it. We just need to override one method. And all we're doing is looking up our NetflixData object and creating a cell. 

Well then, the cell is probably super complex, right?

@implementation MediaCell
-(id)init
{
   self = [super init];
   if(self)
   {
      _gridView = [[IGGridView alloc]init];  
      _gridView.allowHorizontalBounce = YES;  
      _gridView.alwaysBounceVertical = NO;
      _gridView.alwaysBounceHorizontal = YES;  
      _gridView.headerHeight = 0;
      _gridView.rowSeparatorHeight = 0;
      _gridView.rowHeight = 200;
      _gridView.columnWidth = [[IGColumnWidth alloc]initWithWidth:150];
      [self addSubview:_gridView];
 
      IGGridViewImageColumnDefinition* col = [[IGGridViewImageColumnDefinition alloc]initWithKey:@"imgUrl" forPropertyType:IGGridViewImageColumnDefinitionPropertyTypeURL];  
     
       col.cacheImages = YES;
       _ds = [[IGGridViewSingleRowSingleFieldDataSourceHelper alloc]initWithField:col];
       
      _gridView.dataSource = _ds;
   }
   return self;
}
 
-(void)cellAttached
{
   _ds.data = self.data.media;
   [_gridView updateData];
   _gridView.contentOffset = CGPointMake(self.data.scrollPosition, 0);
}
 
-(void)cellDetached
{
    self.data.scrollPosition = _gridView.contentOffset.x;
   _ds.data = nil;
   [_ds invalidateData];
   [_gridView updateData];
   self.data = nil;
}
 
-(void)setupSize:(CGSize)size
{
   _gridView.frame = CGRectMake(0, 0, size.width, size.height);
}
@end

Nope :). You just need override 4 methods:

The first is the constructor. All we're doing there is creating our gridView, and setting some styling properties. We also create our DataSourceHelper and assign it to grid. If you notice we are going to use a built in ColumnDefintion which was designed to handle images. It even knows how to automatically handle urls, so we get all of that logic for free. 

Next we override cellAttached, which simply lets us know that the cell is currently being used. The grid is very efficient when it comes to displaying data. So only cell in view are currently created. Thus we can use this method to setup our cell with the current data, as the data that was previously attached to it might not have been the same.  In this case, we're going hook our data up to the dataSource that awe already created, tell the grid that it should update whats in view,and also tell it the new scroll position. 

Since we've handled loading, we should also handle unloading. Meaning when a cell is no longer in view, we should do some cleanup, such as unhook our datasource and store off the scroll position. 

And finally, the parent grid will tell the cell how big its allowed to be, so we override setupSize to update the size of our child grid. 

And thats it!

Lets run our project and see what it looks like!

Pretty awesome, right?

With very little code, and basically no logic, we've achieved the Netflix UI. 

You can grab the full project here. 

I hope you enjoyed this post, and if you want to see even more of the power of NucliOS, check our our free samples app in the iOS app store!

By Stephen Zaharuk (SteveZ)