Silver Challenge... is it a footer?


#1

I’m not sure exactly what you’re asking in the Silver Challenge for this chapter. Do you want us to set up a footer? I’ve got it to display “no more items” after the last section in the table (no footer for the other sections).

- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section
{
    if (section == [[[BNRItemStore sharedStore] tableHeadings] count] - 1) {
        return @"No more items";
    } else {
        return @"";
    }
}

(tableHeadings is a method which returns an array of section headings (less than $50, greater than $50) for the data.)

I suspect you’re looking to have us do something else, because you titled the challenge “Constant Rows”. I’m having trouble thinking what that might be, apart from some sort of logic in the BNRItemStore which creates a section at the end with one item called “no more items”. That would seem like it would be doing violence to the concept of a “store”, as “no more items” isn’t really an item…

If you had something else in mind, can you nudge me in the right direction?


#2

Sure. A footer is a clever solution, but really I’m looking for: “If there are 10 items in the ItemStore, then there are 11 rows in the table view and that last row always displays the same data.” The idea behind this challenge is that a table view doesn’t simply show an array, but shows what the data source tells it to show. The data source can do all sorts of clever things as long as it returns a number of rows and a cell for each row.


#3

Thanks Joe, that helped.

My code’s a bit messy from the previous challenge’s multiple sections, but it seems to work. It’s attached below. It feels like it’s far too rigid—there’s too much hard-coded stuff—but I guess I want to keep moving. If anyone else has solutions, do share…

- (NSInteger)tableView:(UITableView *)tableView 
 numberOfRowsInSection:(NSInteger)section
{
    // If there are separate sections, add switches here. If not, just use allItems.
    switch (section) {
        case 0:
            return [[[BNRItemStore sharedStore] expensiveItems] count];
            break;
        case 1:
            return [[[BNRItemStore sharedStore] cheapItems] count];
            break;
        case 2: // This is the last row, "no more items"
            return 1;
            break;
        default:
            return [[[BNRItemStore sharedStore] allItems] count] + 1; // Or add one for "no more items"
            break;
    }
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
    if (section == 2) {
        return @""; // No header for "no more items" row.
    } else {
        return [[[BNRItemStore sharedStore] tableHeadings] objectAtIndex:section];
    }
}

- (UITableViewCell *)tableView:(UITableView *)tableView 
         cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Check for a reusable cell, and if not found, create a new one
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UITableViewCell"];
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                      reuseIdentifier:@"UITableViewCell"];
    }
   
    if ([tableView numberOfSections] > 1) {
        if ([indexPath section] == 2) {
            [[cell textLabel] setText:@"no more items"];
        } else if ([indexPath section] == 1) {
            BNRItem *p = [[[BNRItemStore sharedStore] cheapItems] objectAtIndex:[indexPath row]];
            [[cell textLabel] setText:[p description]];
        } else if ([indexPath section] == 0) {
            BNRItem *p = [[[BNRItemStore sharedStore] expensiveItems] objectAtIndex:[indexPath row]];
            [[cell textLabel] setText:[p description]];
        } else {
            return nil; // Won't happen.
        }
    } else { // If no sections, add a solo constant cell at the end.
        int rowsInSection = [tableView numberOfRowsInSection:[indexPath section]] - 1;
        if (rowsInSection == [indexPath row]) {
            [[cell textLabel] setText:@"no more items"];
        } else {
            BNRItem *p = [[[BNRItemStore sharedStore] cheapItems] objectAtIndex:[indexPath row]];
            [[cell textLabel] setText:[p description]];
        }
    }    
    return cell;
}

#4

Yup, that’ll work. Sometimes table view data sources just end up looking like that. When things get really messy, you might decide to have multiple “helper” data sources, where the table view’s real data source does an initial check (like which section is this cell supposed to be in?) and then asks the appropriate helper to prepare the cell.

Also, try making this extra row appear without using a third section… that is, in the 2nd section, have the last row in that section display “no more rows”.


#5

I applied this challenge to the original app(only 1 section), and this is my solution:

[code]- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [[[BNRItemStore sharedStore] allItems] count] + 1;
}

  • (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@“UITableViewCell”];

    if(!cell) {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@“UITableViewCell”];
    }

    if([indexPath row] < [[[BNRItemStore sharedStore] allItems] count]) {
    BNRItem *p = [[[BNRItemStore sharedStore] allItems] objectAtIndex:[indexPath row]];
    [[cell textLabel] setText:[p description]];
    } else {
    [[cell textLabel] setText:@“No more items”];
    }

    return cell;
    }[/code]

I return 1 more row than the itemsList’s size and if the row that I have to generate is in that case, I print “No more items”

I had to put in the .h too


#6

Perfect!


#7

I had this working with one section also, but after reading the above posts I decided to try to take it a step further. I created two arrays. One for above $50 and another for below $51. The ‘no more items’ text is appended to the last section. If either array is empty and there is only one section, the text is appended to that section. It seems to be working, but I very well could have missed something.

Also, when I got to work I realized I had left my thumb drive at home so I downloaded the code up to the point where the challenges begin. Hence the defaultStore instead of sharedStore (in case anyone was wondering).

in BNRItemStore.m

[code]- (NSArray *)allItems
{
return allItems;
}

  • (NSArray *)lessThan
    {
    NSMutableArray *under = [[NSMutableArray alloc] init];
    for (BNRItem *item in allItems) {
    if ([item valueInDollars] < 51) {
    [under addObject:item];
    }
    }
    return under;
    }

  • (NSArray *)moreThan
    {
    NSMutableArray *over = [[NSMutableArray alloc] init];
    for (BNRItem *item in allItems){
    if ([item valueInDollars] >50) {
    [over addObject:item];
    }
    }
    return over;
    }

[/code]
And in ItemsViewController.m

- (int)numberOfSectionsInTableView:(UITableView *)tableView
{
    int i = 0;
    if ([[[BNRItemStore defaultStore] lessThan] count] > 0) {
        i++;
    }
    if ([[[BNRItemStore defaultStore] moreThan] count] > 0) {
        i++;
    }
    return i;
}

- (NSInteger)tableView:(UITableView *)tableView
 numberOfRowsInSection:(NSInteger)section
{
    int i;
    if (section == 0) {
        if ([[[BNRItemStore defaultStore] lessThan] count] == 0) {
            i = [[[BNRItemStore defaultStore] moreThan] count] + 1;
        } else {
            if ([[[BNRItemStore defaultStore] moreThan] count] == 0) {
                i = [[[BNRItemStore defaultStore] lessThan] count] + 1;
            } else {
                i = [[[BNRItemStore defaultStore] lessThan] count];
            }
            
        }
        
        
    }else {
        
        i = [[[BNRItemStore defaultStore] moreThan] count] + 1;
    }
    return i;
}

- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Create an instance of UITableViewCell, with default appearance
    // Check for a reusable cell first, use that if it exists
    UITableViewCell *cell =
        [tableView dequeueReusableCellWithIdentifier:@"UITableViewCell"];

    // If there is no reusable cell of this type, create a new one
    if (!cell) {
        cell = [[UITableViewCell alloc]
                    initWithStyle:UITableViewCellStyleDefault
                  reuseIdentifier:@"UITableViewCell"];
    }
    // Set the text on the cell with the description of the item
    // that is at the nth index of items, where n = row this cell
    // will appear in on the tableview
    if ([indexPath section] == 0) {
        if ([[[BNRItemStore defaultStore] lessThan] count] == 0) {
            if ([indexPath row] < [[[BNRItemStore defaultStore] moreThan] count]) {
                BNRItem *p = [[[BNRItemStore defaultStore] moreThan] objectAtIndex:[indexPath row]];
                [[cell textLabel] setText:[p description]];
            }else {
                [[cell textLabel] setText:@"No more items!"];
            }
            
        }else {
            if ([indexPath row] < [[[BNRItemStore defaultStore] lessThan] count]) {
                BNRItem *p = [[[BNRItemStore defaultStore] lessThan] objectAtIndex:[indexPath row]];
                [[cell textLabel] setText:[p description]];
            } else {
                [[cell textLabel] setText:@"No more items!"];
            }
            
        }
        
    }else {
        if ([indexPath row] < [[[BNRItemStore defaultStore] moreThan] count]) {
            BNRItem *p = [[[BNRItemStore defaultStore] moreThan] objectAtIndex:[indexPath row]];
            [[cell textLabel] setText:[p description]];
        } else {
            [[cell textLabel] setText:@"No more items!"];
        }
        
    }

    return cell;
}

As I said, this appears to work. Please comment if you notice any glaring flaws.

Mark

EDIT: It just hit me- What if BOTH arrays are empty? Not laughing out loud here.
EDIT 2: That wasn’t so difficult after all. Changed numberOfSectionsInTableView: in ItemsViewController

[code]- (int)numberOfSectionsInTableView:(UITableView *)tableView
{
int i = 0;
if ([[[BNRItemStore defaultStore] lessThan] count] > 0) {
i++;
}
if ([[[BNRItemStore defaultStore] moreThan] count] > 0) {
i++;
}

if ([[[BNRItemStore defaultStore] allItems] count] == 0) {
    i++;
}

return i;

}[/code]

Again, If you see any flaws I’d appreciate it if you pointed them out.

Thanks again,
Mark


#8

Hi

I adopted the Predicate solution proposed by one of the brilliant participants here, and tried to do the silver challenge with it.
As I wanted something dynamic, I wanted to “hard-code” as few elements as possible.
Here is my proposal : I tried first to determine if I was in the last section, and in that case I added one row to the number of rows in the datasource.
Then, for the last row of the last section (i.e. the one I added artificially), I labelled the cell “no more items”.

I didn’t really create helper functions to determine in which sections I was, because the methods numberOfSectionsInTableView: and numberOfRowsInSection: included everything I needed : I applied these methods to “self”.

[code]// Bronze / silver challenges

  • (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
    {
    NSInteger i = 0;

    NSPredicate *predicateExpensive = [NSPredicate predicateWithFormat:@“valueInDollars > 50”];
    NSPredicate *predicateCheap = [NSPredicate predicateWithFormat:@“valueInDollars <= 50”];

    if ([[[[BNRItemStore sharedStore] allItems] filteredArrayUsingPredicate:predicateExpensive]count] > 0) {
    i++;
    }

    if ([[[[BNRItemStore sharedStore] allItems] filteredArrayUsingPredicate:predicateCheap]count] > 0) {
    i++;
    }
    return i;
    }

  • (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
    // return [[[BNRItemStore sharedStore] allItems]count];

    NSPredicate *predicate;

    if (section == 0) {
    predicate = [NSPredicate predicateWithFormat:@“valueInDollars > 50”];
    } else {
    predicate = [NSPredicate predicateWithFormat:@“valueInDollars <= 50”];
    }

    // Reached the last section
    if (section == [tableView numberOfSections]-1) {
    return [[[[BNRItemStore sharedStore] allItems] filteredArrayUsingPredicate:predicate]count] + 1;
    } else {
    return [[[[BNRItemStore sharedStore] allItems] filteredArrayUsingPredicate:predicate]count];
    }

}

  • (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
    // Check for a reusable cell first, use that if it exists
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@“UITableViewCell”];

    // If there is no reusable cell of this type, create a new one
    if (!cell) {
    // Create an instance of UITableViewCell, with default appearance
    cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault
    reuseIdentifier:@“UITableViewCell”];
    }

    // Set the text on the cell with the description of the item
    // that is at the nth index of items, where n = row this cell
    // will appear in the tableview

    // BNRItem *p = [[[BNRItemStore sharedStore] allItems]objectAtIndex:[indexPath row]];

    NSPredicate *predicate;

    if ([indexPath section] == 0) {
    predicate = [NSPredicate predicateWithFormat:@“valueInDollars > 50”];
    } else {
    predicate = [NSPredicate predicateWithFormat:@“valueInDollars <= 50”];
    }

    // Last section reached
    if ([indexPath section] == [tableView numberOfSections] - 1) {
    if ([indexPath row] < [tableView numberOfRowsInSection:[indexPath section]] - 1) {
    BNRItem *p = [[[[BNRItemStore sharedStore] allItems]filteredArrayUsingPredicate:predicate]
    objectAtIndex:[indexPath row]];
    [[cell textLabel] setText:[p description]];

      } else {
          // Last row reached
          [[cell textLabel] setText:@"No more items"];
      }
    

    } else {
    BNRItem *p = [[[[BNRItemStore sharedStore] allItems]filteredArrayUsingPredicate:predicate]
    objectAtIndex:[indexPath row]];
    [[cell textLabel] setText:[p description]];
    }

    return cell;
    }

  • (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
    {
    NSString *sectionTitle ;

    if (section == 0) {
    sectionTitle = [NSString stringWithFormat:@“Expensive Items”] ;
    } else {
    sectionTitle = [NSString stringWithFormat:@“Cheap Items”] ;
    }
    return sectionTitle;
    }

[/code]

To everybody : thanks for sharing your ideas, it really helps !