Gold Challenge Gotchas


#1

I found this far easier than the silver challenge of this chapter.

To set up the UI I simply subclassed UITableViewCell like had been done in Chapter 19. I tried to do this without referencing Chapter 19 to see if the process would be intuitive. What caught me were two things. One, I had forgotten that the property outlets for the UILabels needed to be set directly to for the subclassed cell and not the File’s owner. I originally wired them up going through the file’s owner, like you would normally do, and that caused a crash that required rereading Ch 19 carefully. The second gotcha was how you register the custom table view cell for reuse. I had forgotten that it required creating an instance of the nib file, then using this instance to register the custom cell for reuse by the table view. These fixes then allowed me to get my UI up and running properly.

Extracting the upcoming courses was trivial. Examining the JSON data it appeared BNR always sequenced the upcoming courses in order of the dates they were offered, so that allowed me to simply get the first array item under dictionary key “upcoming.” I did test it to verify it was not nil and had at least one sub-dictionary before accessing the data to update the table view cell. If I was writing this to be shipped, I would probably load all upcoming courses, convert the data strings to an NSDate, and verify that I was selected the next upcoming date. For this exercise I just made that assumption.

Here is the code for BNRCoursesViewController:

[code]#import “BNRCoursesViewController.h”
#import “BNRWebViewController.h”
#import “BNRCoursesTableViewCell.h”

@interface BNRCoursesViewController ()

@property (nonatomic, strong) NSURLSession *session;
@property (nonatomic, copy) NSArray *courses;

@end

@implementation BNRCoursesViewController

#pragma mark - Object lifecycle

  • (instancetype)initWithStyle:(UITableViewStyle)style
    {
    self = [super initWithStyle:style];
    if (self) {
    self.navigationItem.title = @“BNR Courses”;
    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
    _session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
    [self fetchFeed];
    }
    return self;
    }

#pragma mark - View lifecycle

  • (void)viewDidLoad
    {
    [super viewDidLoad];

    // Load NIB of custom table view cell and registers the instance with table view for reuse
    UINib *nib = [UINib nibWithNibName:@“BNRCoursesTableViewCell” bundle:nil];
    [self.tableView registerNib:nib forCellReuseIdentifier:@“CoursesCell”];
    }

#pragma mark - NSURLSession

  • (void)fetchFeed
    {
    NSString *requestString = @“https://bookapi.bignerdranch.com/private/courses.json”;
    NSURL *url = [NSURL URLWithString:requestString];
    NSURLRequest *req = [NSURLRequest requestWithURL:url];
    NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:req completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
    NSDictionary *jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
    self.courses = jsonObject[@“courses”];
    NSLog(@"%@", self.courses);

      dispatch_async(dispatch_get_main_queue(), ^{
          [self.tableView reloadData];
      });
    

    }];
    [dataTask resume];
    }

  • (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
    {
    NSURLCredential *cred = [NSURLCredential credentialWithUser:@“BigNerdRanch” password:@“AchieveNerdvana” persistence:NSURLCredentialPersistenceForSession];
    completionHandler(NSURLSessionAuthChallengeUseCredential, cred);
    }

#pragma mark - Table view data source

  • (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
    return [self.courses count];
    }

  • (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
    BNRCoursesTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@“CoursesCell” forIndexPath:indexPath];
    NSDictionary *course = self.courses[indexPath.row];
    cell.courseNameLabel.text = course[@“title”];

    NSArray *upcomingDates = course[@“upcoming”];
    if (upcomingDates && ([upcomingDates count] > 0)) {
    NSDictionary *nextDate = upcomingDates[0];
    cell.nextStartDateLabel.text = nextDate[@“start_date”];
    }
    return cell;
    }

#pragma mark - Table view delegate

  • (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
    {
    NSDictionary *course = self.courses[indexPath.row];
    NSURL *URL = [NSURL URLWithString:course[@“url”]];
    self.webViewController.title = course[@“title”];
    self.webViewController.URL = URL;
    [self.navigationController pushViewController:self.webViewController animated:YES];
    }

@end
[/code]


#2

or if you feel like going the extra mile, you can use these implementations to print every upcoming date for every course

[code]

  • (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
    // return 0;
    NSInteger i = 0;
    for (NSDictionary *course in self.courses) {
    if ([course objectForKey:@“upcoming”]) {
    NSArray *upcoming = [course objectForKey:@“upcoming”];
    for (int j = 0; j < [upcoming count]; j++) {
    i++; // count every upcoming course date
    }
    } else {
    i++; // and count every course without upcoming dates
    }
    }
    return i;
    }

  • (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
    BNRUpcomingTableViewCell *cell =
    [tableView dequeueReusableCellWithIdentifier:@"BNRUpcomingTableViewCell"
    forIndexPath:indexPath];
    NSInteger i = -1;
    for (NSDictionary *course in self.courses) { // for every course in the array of courses received
    if ([course objectForKey:@“upcoming”]) { // if course has upcoming dates
    NSArray *upcoming = [course objectForKey:@“upcoming”];
    for (NSDictionary *upcomingDate in upcoming) { // for every upcoming date
    i++; // count the date as a row
    if (indexPath.row == i) { // if this date’s row is current indexPath
    cell.titleLabel.text = course[@“title”];
    cell.dateLabel.text = [upcomingDate objectForKey:@“start_date”];
    break;
    }
    }
    if (indexPath.row == i) { // if row’s cell values have been assigned
    break;
    }
    } else { // course has no upcoming dates
    i++; // count the course as a row
    if (indexPath.row == i) { // if this course’s row is the current indexPath
    cell.titleLabel.text = course[@“title”];
    cell.dateLabel.text = @“None”;
    break;
    }
    }
    }
    return cell;
    }

  • (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
    {
    NSDictionary *course = [[NSDictionary alloc] init];
    NSInteger i = -1;
    for (NSDictionary *aCourse in self.courses) { // for every course in received array of courses
    if ([aCourse objectForKey:@“upcoming”]) { // if course has upcoming dates
    NSArray *upcoming = [aCourse objectForKey:@“upcoming”];
    for (int j = 0; j < [upcoming count]; j++) {
    i++; // count every upcoming date
    if (indexPath.row == i) { // if date’s row is selected row
    course = aCourse; // use this course
    break;
    }
    }
    if (indexPath.row == i) { // if course has been found
    break;
    }
    } else { // if course has no upcoming dates
    i++; // count the course
    if (indexPath.row == i) { // if course’s row is selected row
    course = aCourse; // use this course
    break;
    }
    }
    }
    NSURL *URL = [NSURL URLWithString:course[@“url”]];

    self.webViewController.title = course[@“title”];
    self.webViewController.URL = URL;
    [self.navigationController pushViewController:self.webViewController
    animated:YES];
    }[/code]
    As you can tell the hardest part is mapping the indexPath to the correct course date. I probably should just make a method to do that but I’ve already wasted enough time getting this far. Enjoy!

edit: it’d also be nice to have these grouped by section. oh well!