Assignment #4 Task #9 Addendum

Please note, this blog entry is from a previous course. You might want to check out the current one.

Your application must work in both portrait and landscape orientations on the iPhone. Support for the iPad is optional (though it will be required next week, so you can save time later by implementing it now). Use appropriate platform-specific UI idioms (e.g., you must use UINavigationControllers to present the information on the iPhone).

We start by deleting everything in the iPad storyboard and copy everything from the iPhone storyboard into the iPad storyboard. Remove the segues from the table views to the photo view controller.

Drag in a split view controller and remove its master and detail view. Instead wire up the tab bar controller as master and the photo view controller as detail view. Add a bar tool to the photo view controller and create an outlet to its class. Finally setup the split view controller as initial view controller.

Reuse SplitViewBarButtonItemPresenter.h from the Psychologist project to define protocol for the presentation of the bar button:

@protocol SplitViewBarButtonItemPresenter <NSObject>
@property (nonatomic, strong) UIBarButtonItem *splitViewBarButtonItem;
@end

and make the photo view controller the SplitViewBarButtonItemPresenter as well as the UISplitViewControllerDelegate:

@interface PhotoViewController () <UIScrollViewDelegate, 
                                   SplitViewBarButtonItemPresenter, 
                                   UISplitViewControllerDelegate>

For the button presenter reuse the respective functions from Psychologist

- (void)handleSplitViewBarButtonItem:(UIBarButtonItem *)splitViewBarButtonItem
{
    NSMutableArray *toolbarItems = [self.toolbar.items mutableCopy];
    if (_splitViewBarButtonItem) [toolbarItems removeObject:_splitViewBarButtonItem];
    if (splitViewBarButtonItem) [toolbarItems insertObject:splitViewBarButtonItem atIndex:0];
    self.toolbar.items = toolbarItems;
    _splitViewBarButtonItem = splitViewBarButtonItem;
}

- (void)setSplitViewBarButtonItem:(UIBarButtonItem *)splitViewBarButtonItem
{
    if (_splitViewBarButtonItem != splitViewBarButtonItem)
        [self handleSplitViewBarButtonItem:splitViewBarButtonItem];
}

and also for the delegate:

- (id <SplitViewBarButtonItemPresenter>)splitViewBarButtonItemPresenter {
    id detailVC = [self.splitViewController.viewControllers lastObject];
    if (![detailVC conformsToProtocol:@protocol(SplitViewBarButtonItemPresenter)]) detailVC = nil;
    return detailVC;
}

- (BOOL)splitViewController:(UISplitViewController *)svc 
   shouldHideViewController:(UIViewController *)vc 
              inOrientation:(UIInterfaceOrientation)orientation
{
    return [self splitViewBarButtonItemPresenter] ? UIInterfaceOrientationIsPortrait(orientation) : NO;
}

- (void)splitViewController:(UISplitViewController *)svc 
     willHideViewController:(UIViewController *)aViewController 
          withBarButtonItem:(UIBarButtonItem *)barButtonItem 
       forPopoverController:(UIPopoverController *)pc
{
    UIBarButtonItem *button = [[UIBarButtonItem alloc] 
        initWithBarButtonSystemItem:UIBarButtonSystemItemSearch 
                             target:barButtonItem.target 
                             action:barButtonItem.action];
    barButtonItem = button;
    [self splitViewBarButtonItemPresenter].splitViewBarButtonItem = barButtonItem;
}

- (void)splitViewController:(UISplitViewController *)svc 
     willShowViewController:(UIViewController *)aViewController 
  invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem
{
    [self splitViewBarButtonItemPresenter].splitViewBarButtonItem = nil;
}

Note that the code above differs slightly. Instead of setting the title of the bar button, the code creates a new button using the system search button.

The last step for the setup of the split view controller is setting its delegate in viewDidLoad and calling the presenter:

    self.splitViewController.delegate = self;
    [self handleSplitViewBarButtonItem:self.splitViewBarButtonItem];

Before (for the iPhone) the photo was fetched from Flickr shortly before the photo view appeared on screen in viewWillAppear. This does not work any more for the split view of the iPad as the photo view will always be on screen. Thus we put that part of the code in a new helper method:

- (void)loadPhoto
{
    NSString *title = @"Photo";
    if (self.photo) title = [FlickrData titleOfPhoto:self.photo];
    self.navigationItem.title = title;
    self.toolbarTitle.title = title;
    NSURL *url = [FlickrFetcher urlForPhoto:self.photo format:FlickrPhotoFormatLarge];
    UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
    self.scrollView.zoomScale = 1.0;
    self.imageView.image = image;
    self.imageView.frame = CGRectMake(0, 0, image.size.width, image.size.height);
    self.scrollView.maximumZoomScale = 10.0;    
    self.scrollView.minimumZoomScale = 0.1;
    self.scrollView.contentSize = image.size;
    
    double wScale = self.scrollView.bounds.size.width / image.size.width;
    double hScale = self.scrollView.bounds.size.height / image.size.height;
    if (wScale > hScale) self.scrollView.zoomScale = wScale;
    else self.scrollView.zoomScale = hScale;
    
    [self.imageView setNeedsDisplay];
}

which also takes care in the different handling of setting the photo title on screen. For the iPhone the title of the navigation controller is used, for the iPad we create a plain tool-bar item for which we create an appropriate outlet.

This function is called in viewWillAppear which provides the functionality for the iPhone

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self loadPhoto];
}

and in setPhoto when the imageView is on screen for the iPad:

...
    if (self.imageView.window)
        [self loadPhoto];        
...

Because there is no segue for the photo view when using the iPad the model for the photo view controller is set when a row is selected:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    id vc = [self.splitViewController.viewControllers lastObject];
    if ([vc isKindOfClass:[PhotoViewController class]]) 
        [vc setPhoto:[self.photos objectAtIndex:indexPath.row]];
}

Because the last viewed photo changes when selecting a photo from the recently-viewed-photos table, its model has to be updated. Thus we put this in another helper function which will be called then the view first appears, and when a photo is selected:

- (void)loadPhotos
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    self.photos = [[defaults objectForKey:RECENTS_PHOTOS_KEY] copy];         
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self loadPhotos];
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    id vc = [self.splitViewController.viewControllers lastObject];
    if ([vc isKindOfClass:[PhotoViewController class]]) {
        [vc setPhoto:[self.photos objectAtIndex:indexPath.row]];
        [self loadPhotos];        
    }
}

The complete code for this task is available at github.

Facebooktwittergoogle_plusredditpinterestlinkedintumblrmailFacebooktwittergoogle_plusredditpinterestlinkedintumblrmail

Flattr this!

Leave a Reply

Your email address will not be published. Required fields are marked *