Challenges


#1

After a fair amount of tweaking I have a solution for both challenges that seems to work fine. Any improvements or problems welcomed!
Silver:
For the toolbar I declare a property in the class extension to BNRWebViewController.m
and declare that the view controller conforms to UIWebViewDelegate protocol.

@interface BNRWebViewController ()<UIWebViewDelegate>

@property (nonatomic, strong)UIToolbar *tBar;

@end

I create a method to configure the toolbar, giving it back and forward buttons (tagged) aimed at a single buttonPressed: method.
The buttons are set as enabled/disabled depending on the web view’s canGoBack/canGoForward property values.

-(void)configureToolbar
{
    //cast the controller's view to a webview
    UIWebView *webview=(UIWebView *)self.view;
    
    //Get  frame for the toolbar
    CGRect tFrame=CGRectMake(0,self.view.bounds.size.height-50, self.view.bounds.size.width, 50);
    
    //Get a toolbar
    UIToolbar *toolbar=[[UIToolbar alloc]initWithFrame:tFrame];
    
    //Get a button with back icon aimed at 'buttonPressed:"
    UIBarButtonItem *backButton=[[UIBarButtonItem alloc]initWithImage:[UIImage imageNamed:@"rXZga"] style:UIBarButtonItemStylePlain target:self action:@selector(buttonPressed:)];
    
    //tag it
    backButton.tag=0;
    
    //set its enabled property to the webview's canGoBack property
    backButton.enabled=webview.canGoBack;
    
    //Get another button and tag it and enable/disable it
    UIBarButtonItem *forwardButton=[[UIBarButtonItem alloc]initWithImage:[UIImage imageNamed:@"x9neS"] style:UIBarButtonItemStylePlain target:self action:@selector(buttonPressed:)];
    forwardButton.tag=1;
    forwardButton.enabled=webview.canGoForward;
    
    //Add them to an array with a flexible space in between
    NSArray *toolbarItems=@[backButton,[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil],forwardButton];
    
    //assign the toolbar items array to the toolbar
    toolbar.items=toolbarItems;
    
    //assign the toolbar to the property
    self.tBar=toolbar;
    
    //add toolbar to the view
    [self.view addSubview:self.tBar];
    
}

The action method for the buttons calls the goBack and goForward methods on the webView

-(void)buttonPressed:(id)sender
{
//cast button from sender
    UIBarButtonItem *button=(UIBarButtonItem *)sender;
//cast webview from view
    UIWebView *webview=(UIWebView *)self.view;
//use tags to move forward/back
    if (button.tag==1) {
        [webview goForward];
    }else{
        [webview goBack];
    }
}

I use a UIWebViewDelegate method to update the toolbar buttons’ status after a page load

-(void)webViewDidFinishLoad:(UIWebView *)webView
{
//get array from toolbar property items array
    NSArray *items=self.tBar.items;
//get buttons from array
    UIBarButtonItem *backButton=items[0];
    UIBarButtonItem *forwardButton=items[2];
//set buttons enabled from webview properties
    backButton.enabled=webView.canGoBack;
    forwardButton.enabled=webView.canGoForward;
    [self configureToolbar];

}

and reconfigure the toolbar on rotation…

-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    [self.tBar removeFromSuperview];
}
-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
    [self configureToolbar];
}

That pretty much does is, unless I’ve missed something. Would probably be simpler using a XIB, but thought I’d try it without.

Gold:
Subclass a tableViewCell with a xib. Add three labels, interface looks like this

#import <UIKit/UIKit.h>

@interface BNRCoursesCell : UITableViewCell

@property (weak, nonatomic) IBOutlet UILabel *titleLabel;
@property (weak, nonatomic) IBOutlet UILabel *dateLabel;
@property (weak, nonatomic) IBOutlet UILabel *instructorLabel;

@end

import the header in BNRCoursesViewController


#import "BNRCoursesViewController.h"
#import "BNRWebViewController.h"
#import "BNRCoursesCell.h"

load and register the nib

-(void)viewDidLoad
{
    [super viewDidLoad];
    
    //Load the nib file
    UINib *nib=[UINib nibWithNibName:@"BNRCoursesCell" bundle:nil];
    
    //Register nib
    [self.tableView registerNib:nib forCellReuseIdentifier:@"BNRCoursesCell"];
}

Fill the cells in cellForRowAtIndexPath

-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    BNRCoursesCell *cell=[tableView dequeueReusableCellWithIdentifier:@"BNRCoursesCell" forIndexPath:indexPath];
    
    NSDictionary *course=self.courses[indexPath.row];
    NSDictionary *details=[course[@"upcoming"] objectAtIndex:0];
    
    cell.titleLabel.text=course[@"title"];
    if (details) {
        cell.dateLabel.text= details[@"start_date"];
        cell.instructorLabel.text=details[@"instructors"];
    }else{
        cell.dateLabel.text= @"TBA";
        cell.instructorLabel.text=@"TBA";
    }
    return cell;
}

Works good on the simulator. Any thoughts welcomed.


#2

I’ve just completed the silver challenge. I think I’ve done something similar to you pmac.

EDIT: I’ve put all my completed code in the post below

One thing I’d like to point out, is that I think it’s better to use setNeedsLayout on the toolbar to update it, rather than add the toolbar as a subview every time.
I’ve noticed a bit of weirdness too. Clicking on some of the links on the BNR website doesn’t count as a reload, so nothing happens to your toolbar.


#3

@pmac72

In your silver solution:

and reconfigure the toolbar on rotation…

After remove the toolbar from its superview, I think you also need to set self.toolbar to nil. Since configureToolbar will create a new instance using the new frame, the old one need to be released.


#4

Thanks Sophie and MonkeyBoy, good points. I know what I did feels like a bit of a hack. I just kept getting 2 toolbars on the screen after rotations and couldn’t work out how to avoid it. I think if it was all done in a xib it would be easier to stay on top of the auto layout of the toolbar. But there must be a better way to do it programmatically rather than hijacking the willRotate/didRotate methods…
will have another look at it later.


#5

OK here’s my complete code for both the silver and gold challenges in one project. I’ve accounted for orientation change too and everything works perfectly (apart from the weirdness mentioned above where webViewDidFinishLoad doesn’t get called when you click on certain links). I didn’t have to implement willRotateToInterfaceOrientation:duration:. The gold challenge was actually pretty easy, having just done the same thing in homepwner.

BNRCoursesViewController.m

#import "BNRCoursesViewController.h"
#import "BNRWebViewController.h"
#import "BNRCourseCell.h"

@interface BNRCoursesViewController () <NSURLSessionDataDelegate>

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

@end

@implementation BNRCoursesViewController

- (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;
}




- (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);
}

- (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)viewDidLoad
{
    [super viewDidLoad];
    
    // Load the NIB file
    UINib *nib = [UINib nibWithNibName:@"BNRCourseCell" bundle:nil];
    
    // Register this NIB, which contains the cell
    [self.tableView registerNib:nib
         forCellReuseIdentifier:@"BNRCourseCell"];
    
    //[self.tableView registerClass:[UITableViewCell class]
     //      forCellReuseIdentifier:@"UITableViewCell"];
}

- (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];
    
}

- (NSInteger)tableView:(UITableView *)tableView
 numberOfRowsInSection:(NSInteger)section
{

    return [self.courses count];
    
}

- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    
    //UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UITableViewCell"
    //                                                        forIndexPath:indexPath];
    
    // Get a new or recycled cell
    BNRCourseCell *courseCell = [tableView dequeueReusableCellWithIdentifier:@"BNRCourseCell"
                                                        forIndexPath:indexPath];
    
    
    NSDictionary *course = self.courses[indexPath.row];
    
    courseCell.course.text = course[@"title"];
    
    if (course[@"upcoming"]) {
        NSArray *array = course[@"upcoming"];
        NSDictionary *dict = array[0];
        
        //NSLog(@"%@", dict);
        courseCell.startDate.text = dict[@"start_date"];
        courseCell.endDate.text = dict[@"end_date"];
        courseCell.location.text = dict[@"location"];
        courseCell.instructors.text = dict[@"instructors"];
    } else {
        courseCell.startDate.text = @"none";
    }
    
    //cell.textLabel.text = course[@"title"];

    
    return courseCell;
}

@end

BNRWebViewController.m

#import "BNRWebViewController.h"
@interface BNRWebViewController () <UIWebViewDelegate>

@property (nonatomic) UIWebView *webView;
@property (nonatomic) NSMutableArray *buttons;
@property (nonatomic) UIToolbar *toolbar;

@end

@implementation BNRWebViewController

- (void)loadView
{
    self.webView = [[UIWebView alloc] init];
    self.webView.scalesPageToFit = YES;
    
    self.webView.delegate = self;
    self.view = self.webView;

}

- (void)viewDidAppear:(BOOL)animated
{
    // To create toolbar, create 3 UIBarButtons in an array. Left, right and flexible space in between.
    // Do this is viewDidAppear to get values for size and position as they = 0 in loadView.
    self.toolbar = [[UIToolbar alloc] init];
    self.toolbar.frame = CGRectMake(0, self.navigationController.navigationBar.frame.size.height + self.navigationController.navigationBar.frame.origin.y, self.view.frame.size.width, 44);
    
    [self createButtons];
    [self.toolbar setItems:self.buttons animated:YES];
    [self.view addSubview:self.toolbar];
    
}

// This is where we create the array for the buttons depending on whether or not we can go back / forward.
- (void)createButtons
{

    UIBarButtonItem *leftButton = [[UIBarButtonItem alloc] initWithTitle:@"Back"
                                                                   style:UIBarButtonItemStylePlain
                                                                  target:self
                                                                  action:@selector(pageBack)];
    UIBarButtonItem *rightButton = [[UIBarButtonItem alloc] initWithTitle:@"Forward"
                                                                    style:UIBarButtonItemStylePlain
                                                                   target:self
                                                                   action:@selector(pageForward)];
    UIBarButtonItem *flexibleSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
    
    if (!_buttons) {
        _buttons = [[NSMutableArray alloc] init];
    }
    
    [self.buttons removeAllObjects];

    
    if (self.webView.canGoBack == YES) {
        [self.buttons addObject:leftButton];
    }
    
    [self.buttons addObject:flexibleSpace];
    
    if (self.webView.canGoForward == YES) {
        [self.buttons addObject:rightButton];
    }
    
    
}

- (void)updateToolbar
{
    self.toolbar.frame = CGRectMake(0, self.navigationController.navigationBar.frame.size.height + self.navigationController.navigationBar.frame.origin.y, self.view.frame.size.width, 44);
    [self createButtons];
    [self.toolbar setItems:self.buttons animated:YES];
    [self.toolbar setNeedsLayout];
}

-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
    [self updateToolbar];
}

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    [self updateToolbar];
}

- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
    [self updateToolbar];
}


- (void)pageBack
{
    [self.webView goBack];
}

- (void)pageForward
{
    [self.webView goForward];
}


- (void)setURL:(NSURL *)URL
{
    _URL = URL;
    if (_URL) {
        NSURLRequest *req = [NSURLRequest requestWithURL:_URL];
        [(UIWebView *)self.view loadRequest:req];
    }
}

@end

BNRCourseCell.h

#import <UIKit/UIKit.h>

@interface BNRCourseCell : UITableViewCell

@property (weak, nonatomic) IBOutlet UILabel *course;
@property (weak, nonatomic) IBOutlet UILabel *startDate;
@property (weak, nonatomic) IBOutlet UILabel *endDate;
@property (weak, nonatomic) IBOutlet UILabel *location;
@property (weak, nonatomic) IBOutlet UILabel *instructors;

@end

BNRCourseCell.xib has 5 connected UILabels for each of the fields and one unconnected UILabel that just says Upcoming: If there is no upcoming course, startDate reads “none” so that it reads: Upcoming: none (I have my startDate label next to my upcoming: label),


#6

I tried both… - (void)webViewDidFinishLoad:(UIWebView *)webView - (void)webViewDidStartLoad:(UIWebView *)webView
I even added an NSLog to see if they are even activating. Nothing on anything I click on either method. Anyone know what’s going on?


#7

[quote=“slassen”]I tried both… - (void)webViewDidFinishLoad:(UIWebView *)webView - (void)webViewDidStartLoad:(UIWebView *)webView
I even added an NSLog to see if they are even activating. Nothing on anything I click on either method. Anyone know what’s going on?[/quote]

sounds like the delegate of your UIWebView object is not set. Try this in BNRWebViewController.m (viewDidLoad)

_webView.delegate = self;

#8

Thank you, pmac72. I couldn’t think of UIWebViewDelegate for the silver challenge. I completed my code with the hints in your post.

#import "BNRWebViewController.h"

@interface BNRWebViewController () <UIWebViewDelegate>

@property (nonatomic, strong) UIToolbar *toolbar;
@property (nonatomic, strong) UIBarButtonItem *backButton;
@property (nonatomic, strong) UIBarButtonItem *forwardButton;

@end

@implementation BNRWebViewController

-(void)loadView
{
    UIWebView *webView = [[UIWebView alloc] init];
    webView.scalesPageToFit = YES;
    webView.delegate = self;
    self.view = webView;
}

-(void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear];
    
    CGRect rect = self.navigationController.navigationBar.frame;
    rect.origin.y += rect.size.height;
    UIToolbar *toolbar = [[UIToolbar alloc] initWithFrame:rect];

    self.backButton =
        [[UIBarButtonItem alloc] initWithTitle:@"Go Back"
                                         style:UIBarButtonItemStylePlain
                                        target:self.view
                                        action:@selector(goBack)];

    self.forwardButton =
        [[UIBarButtonItem alloc] initWithTitle:@"Go Forward"
                                         style:UIBarButtonItemStylePlain
                                        target:self.view
                                        action:@selector(goForward)];
    [self enableButtons];
    toolbar.items = @[self.backButton, self.forwardButton];
    self.toolbar = toolbar;
    [self.view addSubview:toolbar];
}

-(void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
                                        duration:(NSTimeInterval)duration
{
    CGRect rect = self.navigationController.navigationBar.frame;
    rect.origin.y += rect.size.height;
    self.toolbar.frame = rect;
}

-(void)enableButtons
{
    UIWebView *webView = (UIWebView *)self.view;
    self.backButton.enabled = webView.canGoBack;
    self.forwardButton.enabled = webView.canGoForward;
}

-(void)webViewDidFinishLoad:(UIWebView *)webView
{
    [self enableButtons];
}

-(void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
    [self enableButtons];
}

-(void)setURL:(NSURL *)URL
{
    _URL = URL;
    if (_URL) {
        NSURLRequest *req = [NSURLRequest requestWithURL:_URL];
        [(UIWebView *)self.view loadRequest:req];
    }
}

@end

#9

To locate the toolbar at the bottom of the page where it is usually placed, I created this function:

- (void) setToolbarSize
{
    CGRect rect = self.view.frame;
    rect.size.height = 44;
    rect.origin.y = self.view.frame.size.height - rect.size.height;
    
    self.toolbar.frame = rect;
}

I called this when creating the toolbar, and also within willAnimateRotationToInterfaceOrientation: so that the toolbar redisplays upon rotation.


#10

I decided to avoid creating a separate tool bar for the back and forward buttons. Instead I added the back and forward buttons to the the right side of the existing navigation bar (rightBarButtonItems). Hat tips to pmac72 and monkeyboy their code included some elegant solutions.

BNRWebViewController.m

[code]#import “BNRWebViewController.h”

@interface BNRWebViewController()

@property (nonatomic, strong)UIToolbar *toolBar;
@property (nonatomic, strong)UIBarButtonItem *backButton;
@property (nonatomic, strong)UIBarButtonItem *forwardButton;

@end

@implementation BNRWebViewController

  • (void)loadView
    {
    UIWebView *webView = [[UIWebView alloc] init];
    webView.delegate = self;
    webView.scalesPageToFit = YES;
    self.view = webView;
    }

  • (void)viewWillAppear:(BOOL)animated
    {
    [super viewWillAppear:YES];

    self.backButton = [[UIBarButtonItem alloc]initWithTitle:@"<<"
    style:UIBarButtonItemStylePlain
    target:self.view
    action:@selector(goBack)];

    self.forwardButton = [[UIBarButtonItem alloc] initWithTitle:@">>"
    style:UIBarButtonItemStylePlain
    target:self.view
    action:@selector(goForward)];

    self.navigationItem.rightBarButtonItems = @[self.forwardButton, self.backButton];

    [self updateWebViewButtons];
    }

  • (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation) toInterfaceOrientation
    duration:(NSTimeInterval)duration
    {
    CGRect rect = self.navigationController.navigationBar.frame;
    rect.origin.y += rect.size.height;
    self.toolBar.frame = rect;
    }

  • (void)webViewDidFinishLoad:(UIWebView *)webView
    {
    [self updateWebViewButtons];
    }

  • (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
    {
    [self updateWebViewButtons];
    }

  • (void)updateWebViewButtons
    {
    UIWebView *webView = (UIWebView *)self.view;
    self.backButton.enabled = webView.canGoBack;
    self.forwardButton.enabled = webView.canGoForward;
    }

  • (void)setURL:(NSURL *)URL
    {
    _URL = URL;
    if (_URL){
    NSURLRequest req = [NSURLRequest requestWithURL:_URL];
    [(UIWebView
    )self.view loadRequest:req];
    }
    }

@end
[/code]