Paul
Paul

Reputation: 201

iOS - Simple Table Sections with Array Data

I'm just starting with iOS/Xcode and have been Googling/Youtubing for an hour and can't find a matching tutorial. All I'm trying to do right now is display a table with a list of exercises (rows) that are grouped by bodypart (sections). The bodypart sections will never change, but the user will be able to add a custom exercise to a bodypart.

Now, I'm assuming that I need an array for the sections and also an array for exercises...creating those is simple enough. I'm running into a problem assigning exercises to specific sections. Here's an example of the faulty code that when rendered, displays both exercises under both sections...also there aren't any section names being generated in the table so I'm not sure where that comes into play either.

Here's a screenshot of the result (as a side note, not sure why my nav controller isn't rendering): https://i.sstatic.net/A86vS.jpg

Create the individual items:

@property NSString *exerciseName;
@property NSString *exerciseCategoryName;

Create/Allocate the arrays:

@property NSMutableArray *exerciseCategories;
@property NSMutableArray *exercises;

self.exerciseCategories = [[NSMutableArray alloc]init];
self.exercises = [[NSMutableArray alloc]init]; 

Fill the arrays with some default data:

- (void)loadInitialData {
    FNTExerciseCategories *category1 = [[FNTExerciseCategories alloc]init];
    category1.exerciseCategoryName = @"Chest";
    [self.exerciseCategories addObject:category1];
    FNTExerciseCategories *category2 = [[FNTExerciseCategories alloc]init];
    category2.exerciseCategoryName = @"Biceps";
    [self.exerciseCategories addObject:category2];

    FNTExercises *exercise1 = [[FNTExercises alloc]init];
    exercise1.exerciseName = @"Bench Press";
    [self.exercises addObject:exercise1];
    FNTExercises *exercise2 = [[FNTExercises alloc]init];
    exercise2.exerciseName = @"Barbell Curl";
    [self.exercises addObject:exercise2];
}

Load the data:

[self loadInitialData];

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    // Return the number of sections.
    return [self.exerciseCategories count];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // Return the number of rows in the section.
    return [self.exercises count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"ExercisePrototypeCell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

    // Configure the cell...
    MFTExercises *exercise = [self.exercises objectAtIndex:indexPath.row];
    cell.textLabel.text = exercise.exerciseName;
    return cell;
}

Thank you very much to anybody that can chime in!

Upvotes: 1

Views: 3870

Answers (2)

LifeIsHealthy
LifeIsHealthy

Reputation: 347

Actually in tableView:numberOfRowsInSection: you are returning the count of the entire exercises array. So with your sample data you would have two rows per section. Try making an array of exercises for every section and then code something like the following:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // Return the number of rows in the section.
    if (section == 0) {
        return [self.chestExercises count];
    }
    else if (section == 1) {
        return [self.bicepsExercises count];
    }
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"ExercisePrototypeCell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

    // Configure the cell...
    MFTExercises *exercise;
    if (indexPath.section == 0) {
        exercise = [self.chestExercises objectAtIndex:indexPath.row];
    }
    else if (indexPath.section == 1) {
        exercise = [self.bicepsExercises objectAtIndex:indexPath.row];
    }
    cell.textLabel.text = exercise.exerciseName;
    return cell;
}

In this case the chestExercises array would only contain the "Bench Press"-exercise and the bicepsExercises would only contain the "Barbell Curl"-exercise. So you would get one row per section.

For achieving that the sections have titles you would need to implement the method

- (NSString *)tableView:(UITableView *)aTableView titleForHeaderInSection:(NSInteger)section {
    return [self.exerciseCategories objectAtIndex:section];
}

which gives the sections the title according to the names stored in the array.

A more sophisticated way to build your datasource would be to create a NSDictionary with the section names as the keys (bodyparts) and the values being arrays containing the exercises for the bodypart. For instance if your categories are merely strings you could build such a dictionary with your sample data (for the purpose of demonstration I added another exercise):

FNTExerciseCategories *category1 = [[FNTExerciseCategories alloc]init];
category1.exerciseCategoryName = @"Chest";
[self.exerciseCategories addObject:category1];
FNTExerciseCategories *category2 = [[FNTExerciseCategories alloc]init];
category2.exerciseCategoryName = @"Biceps";
[self.exerciseCategories addObject:category2];

FNTExercises *exercise1 = [[FNTExercises alloc]init];
exercise1.exerciseName = @"Bench Press";
FNTExercises *exercise2 = [[FNTExercises alloc]init];
exercise2.exerciseName = @"Barbell Curl";
FNTExercises *exercise3 = [[FNTExercises alloc]init];
exercise3.exerciseName = @"Another Exercise";

// the instance variable self.exercises is a NSMutableDictionary now of course
self.exercises = [[NSMutableDictionary alloc] init];
exercises[category1.exerciseCategoryName] = @[exercise1];
exercises[category2.exerciseCategoryName] = @[exercise2, exercise3];

The advantage here is that you now have one dictionary containing all arrays that contains all your data. So as you're adding more data you don't have to change your implementation of the tableView datasource. BTW I am using Modern Objective-C syntax for the dictionary and arrays.

Having created a dictionary like that you could then simply implement your table view data source like so:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // Return the number of rows in the section.

    // This gives the name of the category at the current section. 
    // It is then used as a key for the dictionary.
    NSString *currentCategory = [[self.exerciseCategories objectAtIndex:section] exerciseCategoryName];
    return [self.exercises[currentCategory] count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"ExercisePrototypeCell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

    // Configure the cell...
    NSString *currentCategory = [[self.exerciseCategories objectAtIndex:indexPath.section] exerciseCategoryName];

    MFTExercises *exercise = [self.exercises[currentCategory] objectAtIndex:indexPath.row];

    cell.textLabel.text = exercise.exerciseName;
    return cell;
}

Using a NSDictionary may or may not benefit your app but you don't have to create an array as instance variable for every body part you have. It may also be more easy to save a single dictionary to disk for persistence.

Upvotes: 3

andykkt
andykkt

Reputation: 1706

First of all, you should practice it with WWDC UITableView section. There are many source code that uses UITableView, UICollectionView and UIScrollView.

What you need in that code is you need to return section header for exerciseCategories, you only defined number of section in - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView this delegate function but you are returning all nil value for the section header at the moment.

- (NSString*)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
    FNTExerciseCategories *category = [self.exerciseCategories objectAtIndex:section];
    return category. exerciseCategoryName;
}

this will display your section. but you need to think about the structure of your data because right now you are not returning correct number for each section you are just returning [self.exercises count] for all section.

And to render the UINavigationController, you need to push the view rather than present view as modal.

[self.navigationController pushViewController:exerciseView animated:YES];

Upvotes: 2

Related Questions