Bronze solution


#1

[ul]BNRItemStore.m[/ul]

[code]//
// BNRItemStore.m
// Homepwner
//
// Created by John T. Shea on 2/19/14.
// Copyright © 2014 John T. Shea. All rights reserved.
//

#import “BNRItemStore.h”
#import “BNRItem.h”

@interface BNRItemStore ()

@property (nonatomic) NSMutableArray *privateItems;

@end

@implementation BNRItemStore

  • (instancetype)sharedStore
    {

    static BNRItemStore *sharedStore = nil;

    // do I need to create a sharedStore?
    if (!sharedStore) {
    sharedStore = [[self alloc]initPrivate];
    }

    return sharedStore;
    }

// if a programmer calls [[BNRItemStore alloc]init], let him
// know2 the error of his ways

  • (instancetype)init
    {

    @throw [NSException exceptionWithName:@“Singleton” reason:@“Use + [BNRItemStore sharedStore]” userInfo:nil];
    return nil;

}

// here is the real (secret) initializer

  • (instancetype)initPrivate
    {
    // create an array for the item store, then add two arrays, one (0) for items over
    // $50 and the other (1) for all other items
    self = [super init];
    if (self) {
    _privateItems = [[NSMutableArray alloc ]init ];
    [_privateItems addObject:[[NSMutableArray alloc]init]];
    [_privateItems addObject:[[NSMutableArray alloc]init]];

    }

    return self;

}

  • (NSArray *)allItems
    {
    return self.privateItems;

}

  • (BNRItem *)createItem
    {
    // if item created is worth more than $50 place in [0] array
    // else place it in [1] array
    BNRItem *item = [BNRItem randomItem];
    if (item.valueInDollars >50.00) {
    [self.privateItems[0] addObject: item];
    }else
    [self.privateItems[1] addObject: item ];

    return item;
    }
    @end
    [/code]

[ul]BNRItemsViewController.m[/ul]

[code]//
// BNRItemsViewController.m
// Homepwner
//
// Created by John T. Shea on 2/19/14.
// Copyright © 2014 John T. Shea. All rights reserved.
//

#import “BNRItemsViewController.h”
#import “BNRItemStore.h”
#import “BNRItem.h”

@implementation BNRItemsViewController

  • (instancetype)init
    {
    // call the superclass’s designated initializer
    self = [super initWithStyle:UITableViewStyleGrouped];
    if (self) {
    for (int i = 0; i < 5; i++) {
    [[BNRItemStore sharedStore]createItem];
    }
    }
    return self;

}

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

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

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

}

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

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

    // 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
    NSArray *items = [[BNRItemStore sharedStore]allItems];
    BNRItem *item = items[indexPath.section][indexPath.row];

    cell.textLabel.text = [item description];

    return cell;

}

  • (void)viewDidLoad
    {
    [super viewDidLoad];

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

}

@end
[/code]


#2

I thought the Bronze Challenge was supposed to be easy. It is a little different than the first solution.

Is there a place we can goto to see a solution for these Challenges provided by Big Nerd Ranch? If not there should be. It is difficult for me to know if I accomplished the challenge in the best way.

I created a new method in BNRItemStore.h called itemsByValue

[code]- (NSArray *)itemsByValue
{
NSMutableArray *itemsUnder50 = [[NSMutableArray alloc] init];
NSMutableArray *itemsOver50 = [[NSMutableArray alloc] init];

NSArray *allItems = [self allItems];

for (BNRItem *item in allItems) {
    if (item.valueInDollars < 50) {
        [itemsUnder50 addObject:item];
    } else {
        [itemsOver50 addObject:item];
    }
}

NSArray *itemsByValue = [NSArray arrayWithObjects:itemsUnder50, itemsOver50, nil];

return itemsByValue;

}
[/code]
Then I added the numberOfSectionsInTableView Method…

-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { NSArray *itemsByValue = [[BNRItemStore sharedStore] itemsByValue]; return [itemsByValue count]; }

In the cellForRowAtIndexPath, I only had to change BNRItem *item =

[code]-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Get a new or recycled cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@“UITableViewCell” forIndexPath:indexPath];

// 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

// I created a new method to return the itemsByValue array
// this is an array that holds two arrays (one < 50 and > 50)
NSArray *itemsByValue = [[BNRItemStore sharedStore] itemsByValue];
BNRItem *item = itemsByValue[indexPath.section][indexPath.row];

cell.textLabel.text = [item description];

return cell;

} [/code]


#3

Looks like you did to me.

You “externalized” itemsValue … I left that inside numberOfSectionsInTableView, but it looks fine.


#4

What you did will work.

But what happens when they want to say… render any item that has a negative price in red or use a special font or icon for a certain type of item ?

You should never mix the data MODEL with how it is VIEWED. Doing so works great the first time but it will quickly make the code hard to manage.

// return a cell object to paint in the UI the contents of the model
- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    // get one from the pool
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UITableViewCell"
                                                            forIndexPath:indexPath];

    // data model item from model
    STItem * item = [[STItemStore sharedStore] allItems] [indexPath.row];
    
    // set UI cell text to render in view for this data model item
    cell.textLabel.text = item.description;

    return cell;
}

All you need to do is determine if the models size is at the end and if the value is > $50. Remember indexPath.row is ZERO BASED but the ‘count’ is ONE BASED.

The VIEW determines how the data is rendered. The MODEL just contains data. If you end up changing your model to make the view happy it is a bad path in my humble opinion.

There are two exceptions to this rule:

  1. If the view needs it sorted it is usually good practice to put the sort on the data model before the ViewController gets it.
  2. The description method is a cheap and easy way to print to the UI. Any object painted in the UI will call the models ‘description’ method.

Kind regards,

Ed


#5

Hello everybody,
I am new to programming and also new to the board here.
Back to topic. This is my solution for the bronze challenge. (maybe it should be a silver challenge)

First: This solution did not touch the models. Just code added to BNRItemsViewController.m

In order to solve the challenge I modified the following instance methods:

[ul]numberOfSectionsInTableView:
tableView:numberOfRowsInSection:
tableView:cellForRowAtIndexPath: [/ul]

And here is the code for numberOfSectionsInTableView:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 2; }
That was pretty simple. Two sections needed, so return 2.

The next method is tableView:numberOfRowsInSection:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // my counting variable and the content of the singleton as an array
    int numberOfRows = 0;
    NSArray *array = [[BNRItemStore sharedStore] allItems];
    
    // code for the first section
    if (section == 0) {
        for (int i = 0; i < array.count; i++)
        {
            BNRItem *item = array[i];
            
            // count items worth more than $50
            if (item.valueInDollars > 50) {
                 numberOfRows++;
            }
        }
        NSLog(@"I am section %d and return %d rows", section, numberOfRows);
        return numberOfRows;
    }
    
    // code for the second section
    if (section == 1) {
        for (int i = 0; i < array.count; i++)
        {
            BNRItem *item = array[i];
            
            // count items worth less or equal than $50
            if (item.valueInDollars <= 50) {
                numberOfRows++;
            }
        }
        return numberOfRows;
    }
    return 0;
}

Take your time… My intention was to create a dynamic number of rows in every section. Every section is looking through the sharedStore array and counts the number of items, that meets the criteria. This number represents the number of rows in the respective section.

Finally the third method… tableView:cellForRowAtIndexPath:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // create an instance of UITableViewCell
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UITableViewCell" forIndexPath:indexPath];
    
    // get the content of BNRItemStore as an array
    NSArray *items = [[BNRItemStore sharedStore] allItems];
    
    // This is only for section 1
    if (indexPath.section == 1) {
        // create a new mutable array
        NSMutableArray *sectionOneContent = [NSMutableArray array];
        for (int i = 0; i < items.count; i++) {
            
            // create a variable for every item
            BNRItem *checkItem = items[i];
            
            // add items to the mutable array
            // that are worth less or equal than $50
            if (checkItem.valueInDollars <= 50) {
                [sectionOneContent addObject:checkItem];
            }
        }
        // now ask the mutable array for the specific row
        BNRItem *item = sectionOneContent[indexPath.row];
        
        cell.textLabel.text = [item description];
        return cell;
    }
    
    // This is only for section 0
    if (indexPath.section == 0) {
        // create a new mutable array
        NSMutableArray *sectionZeroContent = [NSMutableArray array];
        for (int i = 0; i < items.count; i++) {
            
            // create a variable for every item
            BNRItem *checkItem = items[i];
            
            // add items to the mutable array
            // that are worth less or equal than $50
            if (checkItem.valueInDollars > 50) {
                [sectionZeroContent addObject:checkItem];
            }
        }
        // now ask the mutable array for the specific row
        BNRItem *item = sectionZeroContent[indexPath.row];
        
        cell.textLabel.text = [item description];
        return cell;
    }
    // for every other section return a blank cell
    return cell;
}

It may help to see what the indexPath looks like. Because we set two sections in the first method, the indexPath will look like this:
(Remember, the number of rows are dynamically created in the second method. In this example there are 3 rows for section 0 and 2 rows for section 1.)
[ul]Section 0.
Row 0
Row 1
Row 2
Section 1
Row 0
Row 1[/ul]
The indexPath for the first row is 0.0
for the second row 0.1
for the third row 0.2


#6

IMO the model can be responsible for formatting the data - if you have multiple models storing different types of data that you want to show in one table, the models can implement the same protocol which requires them to return their data formatted in a certain way. That way, more models can be added at any time - expandable as OO should be. If your view is changed & requires different formatting, the code would need to be changed anyway, wherever it is formatted.


#7

I am also of the belief that the model should not be touched for this exercise. Changing the model to handle a $50 range is very contrived and as a result very limiting in the real world. The only change I would have considered for the model, of which I did not implement, was a method that takes a filter variable supplied by the controller. In this way the model will work under all conditions with the controller sending it the dollar range to limit to. However, I did not go this route for this exercise and therefore only made changes to the ViewController.

I first created two private properties of type NSMutableArray for lowValue and highValue. I used the getter below to instantiate these:

-(NSMutableArray *)lowValue
{
    if (!_lowValue)
    {
        _lowValue = [[NSMutableArray alloc]init];
    
       NSArray *items = [[BNRItemStore sharedStore]allItems];
    
       for (BNRItem *item in items)
       {
           if ([item valueInDollars]<50)
           {
               [_lowValue addObject:item];
           }
       }
    }
    
    return _lowValue;
}

-(NSMutableArray *)highValue
{
    if (!_highValue)
    {
        _highValue = [[NSMutableArray alloc]init];
    
       NSArray *items = [[BNRItemStore sharedStore]allItems];
    
       for (BNRItem *item in items)
       {
           if ([item valueInDollars]>49)
           {
               [_highValue addObject:item];
           }
       }
    }
    return _highValue;
}

I did add a title to each section which you can see below otherwise the table methods are all pretty generic:

-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 2;
}

-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
    if (section==0) return @"Value Greater Than $50";
    else return @"Value Less Than $50";
}

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    if(section==0) return [self.highValue count];
    else return [self.lowValue count];
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UITableViewCell" forIndexPath:indexPath];
    
    if([indexPath section]==0)
    {
        cell.textLabel.text = [self.highValue[indexPath.row] description];
    }
    else
    {
        cell.textLabel.text = [self.lowValue[indexPath.row] description];
    }

    return cell;
}

Lastly, I had an issue where the top row was showing up in the simulators status bar. After googling around about this I found a statement which I added to my viewDidLoad to solve this issue. I believe if we used an XIB it could have been solved there with a toggle.

self.tableView.contentInset = UIEdgeInsetsMake(20.0f, 0.0f, 0.0f, 0.0f);

#8

Hey All,

I think the main point of this particular challenge was to incorporate a more modular view. I’ve seen some solutions that try to split the two arrays in the View Controller. Correct me if I’m wrong, but I wrote two separate methods to calculate the arrays in BNRStore:

[code]- (NSArray *) fiftyAndAbove
{
NSMutableArray *theArray = [[NSMutableArray alloc] init];
for (BNRItem *item in self.privateItems) {
if (item.valueInDollars >=50) {
[theArray addObject:item];
}
}
NSArray *arrayToReturn = [NSArray arrayWithArray];
return arrayToReturn;
}

  • (NSArray *) fiftyAndBelow
    {
    NSMutableArray *theArray = [[NSMutableArray alloc] init];
    for (BNRItem *item in self.privateItems) {
    if (item.valueInDollars <=50) {
    [theArray addObject:item];
    }
    }
    NSArray *arrayToReturn = [NSArray arrayWithArray];
    return arrayToReturn;
    }
    [/code]

Then, I set up two sections in BNRItemsViewController.m:

[code]- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView
{
return 2;
}

  • (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
    if (section == 0) {
    return [[[BNRItemStore sharedStore] fiftyAndAbove] count];
    }
    else if (section == 1) {
    return [[[BNRItemStore sharedStore] fiftyAndBelow] count];
    }
    return 0;
    }

  • (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
    // Create an instance of UITableViewCell, with default appearance
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@“UITableViewCell” forIndexPath:indexPath];

    // 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) {
    NSArray *items = [[BNRItemStore sharedStore] fiftyAndAbove];
    BNRItem *item = items[indexPath.row];
    cell.textLabel.text = [item description];
    }
    else if (indexPath.section == 1) {
    NSArray *otherItems = [[BNRItemStore sharedStore] fiftyAndBelow];
    BNRItem *otherItem = otherItems[indexPath.row];
    cell.textLabel.text = [otherItem description];
    }

    return cell;

}
[/code]

Looking back, it should not have taken as long as it did. If you think you’ve done this a different/better way let me know! I’d love to learn about it.


#9

To make your table more readable add this code in BNRItemsViewController.m :

-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger) section
{
if(section == 0)
{
return @"Items over $50;
}
else
return @"The Rest of the items;
}

These lines of code will set the title of each section :smiley:


#10

This one took me longer than I think it should’ve. I appreciate you guys posting your code here so others, like me, can see it.

I was thinking there were 2 ways I could do this challenge:

  • inspect each row before or after it was pulled from BNRItem and see what the value was, then put it in section 1 or 2
  • populate the table view completely and then move each cell depending on the value

I started to try both ways and I’m failing miserably. It’s possible I’m over-thinking the solution, that’s happened to me a few times already as well as when I was reading the BNR Objective-C book. I’m getting very frustrated that these challenges are becoming too challenging for me and I won’t be able to rely on my own coding skill when the time comes to make my own app. I don’t want to skip the challenges because I think it’s important to my ability to learn to work on them. I see the solutions you guys are coming up with and I wonder why I’m not able to solve the challenges as well as you.


#11

This is generally the right approach in my view but it depends on what you mean by “after it was pulled”. This is leading me to believe that you are trying to do this after the table pulls it for display which is too late. When the table is populating it is going to do it in order by drawing the first section and the next, etc. It isn’t going to skip around. This is why you want to organize things in the proper order before display.

I would not go this route as it is too much work. As above I think it better to get your order set and then paint the table rather than painting the table, iteration through it, and then trying to move things around.

[quote]
I started to try both ways and I’m failing miserably. It’s possible I’m over-thinking the solution, that’s happened to me a few times already as well as when I was reading the BNR Objective-C book. I’m getting very frustrated that these challenges are becoming too challenging for me and I won’t be able to rely on my own coding skill when the time comes to make my own app. I don’t want to skip the challenges because I think it’s important to my ability to learn to work on them. I see the solutions you guys are coming up with and I wonder why I’m not able to solve the challenges as well as you.[/quote]

I have always found coding to be punctuated with moments where you feel like you are clueless and will never get it to moments where you feel you like you have a strong hold of things and just nailed it. While learning anything new you to tend to seesaw as one chapter you really feel like you are getting it and the next you just sunk in quicksand. When is the last time anyone learned something of any real value and experience zero frustration along the way? Monkey around with the different solutions and even try to create something small from scratch that uses just the lessons learned thus far. You might need to spend a bit more time on a chapter to really get it or perhaps you need some more exercises, etc.

Next, you have no idea how much time it took others, how much googling around they may have done, or even how much prior programming experience they have. It would be a mistake to assume everyone comes to this with exactly the same experience. The beauty of these posts is that they show the number of different ways to solve problems and it is always instructive to understand a variety of approaches, but I would refrain from making judgements as to how easily the solutions came for anyone. Also likely the number of people who post here are a tiny fraction of the number of people who own this book (and I wonder how many of those don’t even make it this far).

Lastly, coding fortunately has far less consequences than saying flying an airplane for the beginner. Don’t worry about whether you can rely on your own skills when you make an app of your own. Look at sites like StackOverflow, etc. The questions in the tens of thousands or more are being asked by people who are writing their own programs. Rarely are you creating anything of size or complexity and not having points (perhaps many many of them) where you reach out to forums like that for answers. Volumes of documentation and reference books exist because no one does it without help. Keeping plugging away. This is how it works.

One final point is that you may also want to combine BNR with other learning options. You may learn more or find it additionally helpful to see an instructor teach this stuff and answer audience questions. In my view the gold standard, which is free, comes from Stanford University (Paul Hegarty). You can get their iOS 7 course free on iTunes. It’s absolutely brilliant and it still boggles my mind that it’s free.

Keep with it and good luck!


#12

Thanks for the words of encouragement, c6silver. I took your advice and started supplementing my learning in the book with doing lessons in other sites. It’s good to see how others approach the same topic.

I’m reading thru the articles at raywenderlich.com, too. I don’t want to get ahead of myself and become even more confused so I’m taking it slow, re-reading sections or chapters in the book until I (think) have a good handle on it. I’ll see where this takes me. I’m learning quite a bit by troubleshooting my mistakes, I can tell you that!


#13

@ C6silver:

Can you break down your solution a little more and dumb it down to the level of someone who eats paste and writes on wide-lined paper. I follow most of what you did, but some of the technical jargon and unincluded code is throwing me off a bit. This needs to go in the BNRItemsViewController.m file, right?

@interface BNRItemStore() @property (nonatomic, retain) NSMutableArray *lowValue; @end