Thursday, July 17, 2014

UITableViewCell with UIScrollView inside

When I started coding in Objective C I had some hard times learning how to create a UITableView application. Luckily things changed over time :).

I decided to create a small sample application that shows how to add a table view to a view controller. This table view has only 2 sections, each with one custom created UITableViewCell.

The project is hosted on GitHub, here.

Structure

The AppDelegate launches the ListsTableViewController, a UIViewController that implements UITableViewDelegate and UITableViewDataSource. In the controller I have implemented only some of the methods from the protocols.

I usually use a second class to add the interface elements to my view controllers. In this case, the class is called ListsTVCInterfaceTool. It has a private property of type ListsTableViewController a method called AddTableView.

Below I have listed the code for the ListsTVCInterfaceTool.h:


#import <Foundation/Foundation.h> 
#import "ListsTableViewController.h" 

@class ListsTableViewController; 
@interface ListsTVCInterfaceTool : NSObject 

@property ListsTableViewController *controller; 

//making sure that it will be used only for the ListsTableViewController 
-(id)initWithController:(ListsTableViewController*) controller; 

//Used to add the table view to the controller 
-(void)AddTableView; 

@end
I could have added a parameter of type ListsTableViewController to the AddTableView method, but I preferred to make it a private property so that if we want to add other interface elements with different methods, we don't have to specify the same parameter everywhere. We just init the class once and pass the controller to it.

Since also the view controller will have a reference to this class I have used the @class directive to avoid a circular dependency.

The code for the ListsTVCInterfaceTool.m looks like this:

-(id)initWithController:(ListsTableViewController *)controller 

{ 

self = [super init]; 

if (self) { 

_controller = controller; 

} 

return self; 

} 



-(void)AddTableView 

{ 

CGFloat width = 0; 

CGFloat height = 0; 

width = [[UIScreen mainScreen] bounds].size.width; 

height = [[UIScreen mainScreen] bounds].size.height; 



CGRect rect = CGRectMake(0, 0, width, height); 

_controller.tableView = [[UITableView alloc] initWithFrame:rect]; 

[_controller.tableView setTableFooterView:[[UIView alloc] initWithFrame:CGRectZero]]; 

[_controller.view addSubview:_controller.tableView]; 

_controller.tableView.dataSource = _controller; 

_controller.tableView.delegate = _controller; 

[_controller.tableView setUserInteractionEnabled:YES];   



} 



Some things to notice here:

[_controller.tableView setTableFooterView:[[UIView alloc] initWithFrame:CGRectZero]];

The above line will tell the table view to display only a number of rows equal to the rows in the datasource.  If this line is commented out we would see in the table view more than 2 lines, because it will try to fill the empty space with "fake" rows. I say fake, because they are there only to fill the gap between the last row from the datasource and lower bounds of the view.

_controller.tableView.dataSource = _controller; 
_controller.tableView.delegate = _controller;

The 2 lines of code will set the ListsTableViewController as the delegate and the datasource for the tableview.

Now that we saw what the interface tool class looks like, let's get back to the UITableViewController, the ListsTableViewController.

The code for the ListsTableViewController.m file is this:



- (void)viewDidLoad

{

[super viewDidLoad];



[_tableView registerClass:[AnimalListTableViewCell class] forCellReuseIdentifier:CELL_IDENTIFIER_ANIMAL];



[_tableView registerClass:[CarListTableViewCell class] forCellReuseIdentifier:CELL_IDENTIFIER_CAR];





ListsTVCInterfaceTool *interfaceTool = [[ListsTVCInterfaceTool alloc] initWithController:self];

[interfaceTool AddTableView];

}





#pragma mark - Table view data source



- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView

{

// Return the number of sections.

return 2;

}



- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

{

// Return the number of rows in the section.

return 1;

}



- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

{



UITableViewCell *cell = nil;

if (indexPath.section == 0) {

cell = [tableView dequeueReusableCellWithIdentifier:CELL_IDENTIFIER_CAR];

}

else if (indexPath.section == 1)

{

cell = [tableView dequeueReusableCellWithIdentifier:CELL_IDENTIFIER_ANIMAL];

}



if (!cell) {

if (indexPath.section == 0) {

cell = [[CarListTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CELL_IDENTIFIER_CAR];

}

else if (indexPath.section == 1)

{

cell = [[AnimalListTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CELL_IDENTIFIER_ANIMAL];

}

}

return cell;



}



-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath

{

return TABLEVIEW_CELL_HEIGHT;

}
The ViewDidLoad method registers the 2 custom cells and calls the method to add the table view to the controller.
In the cellForRowAtIndexPath method I display one cell type or the other, depending on the section I am in.

All the upper case strings here are constants defined in the Constants file.

Let's take a look at the CarListTableViewCell.m file:


@implementation CarListTableViewCell
@synthesize ScrollView = _ScrollView;

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{

    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {

        // Initialization code
ListManager *manager = [[ListManager alloc] init];
models = [manager GetCars];

        [self arrangeInterface];
}

    return self;
}


#pragma mark - Interface elements

-(void)arrangeInterface
{

    CGFloat width = self.frame.size.width * [models count];
CGFloat height = TABLEVIEW_CELL_HEIGHT;

    _ScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, height)];
_ScrollView.delegate = self;
_ScrollView.indicatorStyle = UIScrollViewIndicatorStyleBlack;
_ScrollView.alwaysBounceHorizontal = YES;
[_ScrollView setUserInteractionEnabled:YES];
_ScrollView.scrollEnabled = YES;
_ScrollView.pagingEnabled = YES;

    _ScrollView.contentSize = CGSizeMake(width, height);


    for (int i = 0; i < [models count]; i++) {
CarModel *model = (CarModel*)[models objectAtIndex:i];
[self GetViewFromModel:model andIndexOnScrollView:i];

    }

    [self.contentView addSubview:_ScrollView];

}


-(void)GetViewFromModel:(CarModel*)model andIndexOnScrollView:(int)index
{

    CGFloat width = self.frame.size.width;
CGFloat height = _ScrollView.frame.size.height;
CGFloat x =  width * index;
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(x, 0, width, height)];

    NSArray *colors = [NSArray arrayWithObjects:[UIColor orangeColor], [UIColor greenColor], [UIColor blueColor],[UIColor redColor], nil];

    view.backgroundColor = (UIColor*)[colors objectAtIndex:index];

    [self AddElementsToView:view fromModel:model];
[_ScrollView addSubview: view];
}


#pragma mark - Labels

-(void)AddElementsToView:(UIView*)view fromModel:(CarModel*)model
{

    [self AddBrandLabelToView:view fromModel:model];
[self AddModelLabelToView:view fromModel:model];
[self AddYearLabelToView:view fromModel:model];

}
In the constructor I load the models array from the manager. The method to populate the array brings 3 objects of type CarModel.
Next, I arrange the interface. I first setup the UIScrollView and add to it 3 views, each with the details of the corresponding model.

The project is hosted on GitHub, here.

No comments:

Post a Comment