All challenges solution


#1

Ok, here’s my code. I’ll just post the BNRItemsViewController.m. Nothing new in the rest of files, except maybe the background image.

The tricky part I found, was identifying if you are in the last section and the last row wile respecting M-V-C paradigm.

[code]#import “BNRItemsViewController.h”
#import “BNRItem.h”
#import “BNRItemStore.h”

@implementation BNRItemsViewController

#define SECTION_PRICE 50.0

  • (instancetype)init
    {
    self = [super initWithStyle:UITableViewStylePlain];

    if (self) {
    for (int i = 0; i < 5; i++) {
    [[BNRItemStore sharedStore] createItem];
    }
    }

    return self;
    }

  • (instancetype)initWithStyle:(UITableViewStyle)style
    {
    return [self init];
    }

  • (void)viewDidLoad
    {
    [super viewDidLoad];

    [self.tableView registerClass:[UITableViewCell class]
    forCellReuseIdentifier:@“UITableViewCell”];
    [self settingTableViewImage];
    }

  • (void)settingTableViewImage
    {
    UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@“background”]];
    self.tableView.backgroundView = imageView;
    }

#pragma mark - Table Data Source Protocol

//only works for a max of 2 sections

  • (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
    {
    NSInteger numberOfSections = 1;
    BOOL existsItemUnder50;
    BOOL existsItemOver50;

    NSArray *items = [[BNRItemStore sharedStore] allItems];

    for (BNRItem *item in items) {
    if (item.valueInDollars > SECTION_PRICE) {
    existsItemOver50 = YES;
    } else if (item.valueInDollars <= SECTION_PRICE) {
    existsItemUnder50 = YES;
    }
    }

    if (existsItemOver50 && existsItemUnder50) numberOfSections = 2;

    return numberOfSections;
    }

  • (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
    NSArray *items = [[BNRItemStore sharedStore] allItems];

    NSInteger numberOfRows = 0;

    for (int i = 0; i < [items count]; i++) {
    BNRItem *item = items[i];

      if (section == 0) {
          if (!(item.valueInDollars > SECTION_PRICE)) {
              numberOfRows ++;
          }
      } else if (section == 1) {
          if (item.valueInDollars > SECTION_PRICE) {
              numberOfRows ++ ;
          }
      }
    

    }

    if (section == [self lastSectionPosition]) numberOfRows ++;

    return numberOfRows;
    }

  • (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UITableViewCell"
    forIndexPath:indexPath];

    NSArray *items = [[BNRItemStore sharedStore] allItems];

    for (BNRItem *item in items) {
    if (indexPath.section == 0) {
    if (item.valueInDollars <= SECTION_PRICE) {
    cell.textLabel.text = [item description];
    cell.textLabel.font = [UIFont systemFontOfSize:20];
    }
    } else if (indexPath.section == 1) {
    if (item.valueInDollars > SECTION_PRICE) {
    cell.textLabel.text = [item description];
    cell.textLabel.font = [UIFont systemFontOfSize:20];
    }
    }
    }

    if ((indexPath.section == [self lastSectionPosition]) && (indexPath.row == [self lastRowPosition])) {
    cell.textLabel.text = @“No more items!”;
    }

    return cell;
    }

-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
NSMutableString *title;
if (section == 0) {
title = [NSMutableString stringWithString:@“Under 51$”];
} else if (section == 1) {
title = [NSMutableString stringWithString:@“Over 50$”];
}
return [title copy];
}

  • (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
    {
    CGFloat rowHeight = 60.0;
    NSInteger lastSectionPosition = [self lastSectionPosition];
    NSInteger lastRowPosition = [self lastRowPosition];

    if ((indexPath.row == lastRowPosition) && (indexPath.section == lastSectionPosition)) {
    rowHeight = 44.0;
    }

    return rowHeight;
    }

//tells whats the position for the last section.
//There is always at least one section.

  • (NSInteger)lastSectionPosition
    {
    NSArray *items = [[BNRItemStore sharedStore] allItems];

    NSInteger lastSectionPosition = 0;
    BOOL existsItemUnderSectionPrice;
    BOOL existsItemOverSectionPrice;

    for (int i = 0; i < [items count]; i++) {

      BNRItem *item = items[i];
    
      if (item.valueInDollars <= SECTION_PRICE) existsItemUnderSectionPrice = YES;
      if (item.valueInDollars > SECTION_PRICE) existsItemOverSectionPrice = YES;
    

    }

    if (existsItemOverSectionPrice && existsItemUnderSectionPrice) lastSectionPosition = 1;

    return lastSectionPosition;
    }

//Tells the last position of the last row in the last section
//Only works if we have a max of 2 sections!!

  • (NSInteger)lastRowPosition
    {
    NSArray *items = [[BNRItemStore sharedStore] allItems];

    NSInteger lastRowPosition = 0;

    for (int i = 0; i < [items count]; i++) {

      BNRItem *item = items[i];
      
      if ([self lastSectionPosition] == 0) {
          if (!(item.valueInDollars > SECTION_PRICE)) {
              lastRowPosition ++;
          }
      } else if ([self lastSectionPosition] == 1) {
          if (item.valueInDollars > SECTION_PRICE) {
              lastRowPosition ++ ;
          }
      }
    

    }

    return lastRowPosition;
    }

@end
[/code]


#2

I think I understand what you’re doing, but its a bit worrying that it will only work for 2 sections. My solution I think is extensible for more sections.
I declare a new array in the class extension which will supply the items from the store in a new format,

[code]@interface BNRItemsViewController ()

@property (nonatomic, strong) NSMutableArray *itemsForList;

@end
[/code]

Then in viewWillAppear I fill that array with two arrays from the store data divided using the valueInDollars value.
The arrays are added to the itemsForList such that the itemsOverFifty will always take index 1 in the array (unless it is the only array, in which case it will be at index 0).
Also, if there are no items under 50, or no items over 50 the corresponding arrays will not be created, so we will not have messy empty sections in the tableview. BUT if there are no items I create an empty array for itemsForList so there is at least one section for the “No More Items” row…

[code]
-(void)viewWillAppear:(BOOL)animated
{
NSArray *items=[[BNRItemStore sharedStore]allItems];

NSMutableArray *itemsUnderFifty;
NSMutableArray *itemsOverFifty;
NSMutableArray *sortedItems=[[NSMutableArray alloc]init];


for (BNRItem *i in items) {
    if (i.valueInDollars<50) {
        if (!itemsUnderFifty) {
            itemsUnderFifty=[[NSMutableArray alloc]init];
            [sortedItems insertObject:itemsUnderFifty atIndex:0];
        }
                    [itemsUnderFifty addObject:i];
    }else{
        if(!itemsOverFifty){
            itemsOverFifty=[[NSMutableArray alloc]init];
            [sortedItems insertObject:itemsOverFifty atIndex:[sortedItems count]];
        }
                    [itemsOverFifty addObject:i];
    }
}
if (!itemsUnderFifty && !itemsOverFifty) {
    [sortedItems addObject:[NSArray array]];
}

self.itemsForList=sortedItems;
itemsOverFifty=nil;
itemsUnderFifty=nil;

}[/code]

Number of sections is simply a count of the items in the itemsForList array… so it could be many more arrays (sections) if needed…

-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return [self.itemsForList count]; }

Number of rows is similar, but if it is the last or only section an extra row is added…(for the “no more items”

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    int numberOfRows;
    if (section==[[self itemsForList] count]-1) {
        numberOfRows=[[[self itemsForList] objectAtIndex:section] count]+1;
    }else{
        numberOfRows=[[[self itemsForList] objectAtIndex:section] count];
    }
    return numberOfRows;
}

Then I fill the cells. First check if its the last row in the last section - if it is it says “No more rows” and configures font size etc.
If not fill sections/rows from corresponding indexes of itemsForList array.

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//Allocate a cell
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UITableViewCell" forIndexPath:indexPath];
    
//If the cell is the last cell in the last section it displays "No more items"
    if ([indexPath section]==[[self itemsForList] count]-1) {
        if ([indexPath row]==[[self.itemsForList objectAtIndex:[indexPath section]] count]) {
            cell.textLabel.text=@"No more items!";
            cell.backgroundColor=[UIColor colorWithRed:1.0 green:0.0 blue:0.1 alpha:0.4];
            cell.textLabel.font=[UIFont systemFontOfSize:14];
//And returns that cell
            return cell;
        }
    }
//Otherwise teh cell is configured from 'itemsForList'
    NSArray *items=[self.itemsForList objectAtIndex:[indexPath section]];
    BNRItem *item=[items objectAtIndex:[indexPath row]];
    
//
    cell.textLabel.text=[item description];
    cell.backgroundColor=[UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:0.2];
    cell.textLabel.font=[UIFont systemFontOfSize:20];
    
    return cell;
}

The height of the rows is set using the same last row calculation as before…

[code]
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([indexPath section]==[[self itemsForList] count]-1) {
if ([indexPath row]==[[self.itemsForList objectAtIndex:[indexPath section]] count]) {
return 44;
}
}
return 60;

}[/code]

The background image is set in viewDidLoad, and in cellForRow etc. I had set the cell backgrounds semitransparent so you could see it coming through…

[code]-(void)viewDidLoad
{
[super viewDidLoad];

UIImageView *bg=[[UIImageView alloc]initWithImage:[UIImage imageNamed:@"HomeBack"]];
self.tableView.backgroundView=bg;


[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"UITableViewCell"];

}[/code]

It works so far! Seems simple and extensible enough, but if anyone thinks things are happening in the wrong place I’d be happy to be told!


#3

I feel really really silly, because I was struggling with setting the background image. For some reason I completely forgot about the existence of UIImageView :blush: . I was trying to figure out how to turn an UIImage into a view, haha.

Anyways, here’s what i came up with for the gold challenge. The way I did the silver challenge made the first two steps of this gold challenge really quite easy to be honest!

I really just made changes to BNRItemsViewController.m’s tableview related methods, so will just paste those in here:

[code]#pragma mark - Tableview methods

  • (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
    NSArray *theItems = [[BNRItemStore sharedStore] allItems];
    NSArray *sectionItems = theItems[section];
    NSInteger sum = [sectionItems count] +1;
    return sum;
    }

  • (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
    {
    NSArray *theItems = [[BNRItemStore sharedStore] allItems];
    return [theItems count];
    }

  • (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
    // Create a UIImage, use that in the UIImageView which is then assigned as the backgroundView
    UIImage *backgroundImage = [UIImage imageNamed:@“background.jpg”];
    UIImageView *backgroundView = [[UIImageView alloc] initWithImage:backgroundImage];
    tableView.backgroundView = backgroundView;

    // Create an instance of UITableViewCell, with default appearance
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UITableViewCell"
    forIndexPath:indexPath];

    // Get an array of allItems and count the instances of BNRItem in each array
    NSArray *items = [[BNRItemStore sharedStore] allItems];
    NSInteger sumOne = [items[0] count];
    NSInteger sumTwo = [items[1] count];

    // Create a pointer to a UIFont with default font and fontsize of 20
    UIFont *font = [UIFont fontWithName:nil size:20];

    // Iterate over the sections and rows, inserting a plaintext into the last row
    if (indexPath.section == 0) {
    if (indexPath.row == sumOne) {
    cell.textLabel.text = @“No more items!”;
    } else {
    cell.textLabel.font = font;
    BNRItem *item = items[indexPath.section][indexPath.row];
    cell.textLabel.text = [item description];
    }
    }
    if (indexPath.section == 1) {
    if (indexPath.row == sumTwo) {
    cell.textLabel.text = @“No more items!”;
    } else {
    cell.textLabel.font = font;
    BNRItem *item = items[indexPath.section][indexPath.row];
    cell.textLabel.text = [item description];
    }
    }

    return cell;
    }

  • (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
    {
    CGFloat rowHeight = 0;

    NSArray *items = [[BNRItemStore sharedStore] allItems];
    NSInteger sumOne = [items[0] count];
    NSInteger sumTwo = [items[1] count];

    if (indexPath.section == 0) {
    if (indexPath.row == sumOne) {
    rowHeight = 44;
    } else {
    rowHeight = 60;
    }
    }

    if (indexPath.section == 1) {
    if (indexPath.row == sumTwo) {
    rowHeight = 44;
    } else {
    rowHeight = 60;
    }
    }

    return rowHeight;
    }
    [/code]

Because I already separated the last row from the rest of the rows editing the font was really quite easy.
Strange enough when I tried to edit the row height in the - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath method, it wouldn’t stick. Simply nothing happened. So I just implemented the - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath method instead to get the row height right, recycling some of the code I already used in the previous method.