cs193p – Assignment #5 Task #6

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

When the user chooses a photo from any list, show it inside a scrolling view that allows the user to pan and zoom (a reasonable amount). You obtain the URL for a Flickr photo’s image using FlickrFetcher’s URLForPhoto:format:.

Start by adding a new view controller to storyboard. Add a scroll view and an activity indicator (you might want to change the color of the later one). Add push segues from the cells of the last table view controllers of both tabs to the new controller (and name them):

cs193p – assignment #5 task #6 - image view controller
cs193p – assignment #5 task #6 – image view controller


Create a newsub class derived from the view controller class with a single public property holding the URL of the image:

@property (nonatomic, strong) NSURL *imageURL;

Link the recently created view controller to this new class and create outlets for the scroll view and the activity indicator:

@property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *spinner;

Add two other private properties to hold the image view and the actual image:

@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) UIImage *image;

Do the basic setup of the scroll view inside its setter. Set the minimum and maximum zooms scales to allow zooming. Make the class the delegate for the scroll view and set the content size of the scroll view if an image is available to its size:

- (void)setScrollView:(UIScrollView *)scrollView
{
    _scrollView = scrollView;
    _scrollView.minimumZoomScale = 0.2;
    _scrollView.maximumZoomScale = 2.0;
    _scrollView.delegate = self;
    _scrollView.contentSize = self.image ? self.image.size : CGSizeZero;
}

In addition to setting the class as delegate it needs to declare that is able to actually be such a delegate:

@interface ImageVC () <UIScrollViewDelegate>

The only delegate method we need to declare which view allows zooming and panning – the image view:

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
    return self.imageView;
}

Instantiate this image view lazily:

- (UIImageView *)imageView
{
    if (!_imageView) {
        _imageView = [[UIImageView alloc] init];
    }
    return _imageView;
}

… and it needs to be added to the scroll view:

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.scrollView addSubview:self.imageView];
}


The image property will not store the image itself, but use the getter and setter to access the image in the image view, and additionally setup its surroundings. Reset the zoom scale of the scroll view, the sizes of the image and the scroll view ... and stop the activity indicator:


- (UIImage *)image
{
    return self.imageView.image;
}

- (void)setImage:(UIImage *)image
{
    self.scrollView.zoomScale = 1.0;
    self.imageView.image = image;
    [self.imageView sizeToFit];
    self.imageView.frame = CGRectMake(0, 0, image.size.width, image.size.height);
    self.scrollView.contentSize = self.image ? self.image.size : CGSizeZero;
    [self.spinner stopAnimating];
}

When the URL for the image gets set, start the activity indicator and download the image. When done set the image, and the code above will do the rest of the setup:

- (void)fetchImage
{
    self.image = nil;
    if (!self.imageURL) return;
    
    [self.spinner startAnimating];
    
    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]];
    NSURLSessionDownloadTask *task = [session downloadTaskWithURL:self.imageURL
                                                completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
                                                    if (!error) {
                                                        if ([response.URL isEqual:self.imageURL]) {
                                                            UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:location]];
                                                            dispatch_async(dispatch_get_main_queue(), ^{
                                                                self.image = image;
                                                            });
                                                        }
                                                    }
                                                }];
    [task resume];
}

- (void)setImageURL:(NSURL *)imageURL
{
    _imageURL = imageURL;
    [self fetchImage];
}

The URL and the title for the image are set when preparing the segue from the table view cell:

- (void)prepareImageVC:(ImageVC *)vc
              forPhoto:(NSDictionary *)photo
{
    vc.imageURL = [FlickrHelper URLforPhoto:photo];
    vc.title = [FlickrHelper titleOfPhoto:photo];
}

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
    if ([segue.identifier isEqualToString:@"Show Photo"] && indexPath) {
        [self prepareImageVC:segue.destinationViewController
                    forPhoto:self.photos[indexPath.row]];
    }
}

Though the command to retrieve the URL is not really complicated, in order to keep all Flickr specific commands in one place use a tiny helper method:

+ (NSURL *)URLforPhoto:(NSDictionary *)photo
{
    return [FlickrHelper URLforPhoto:photo format:FlickrPhotoFormatLarge];
}

The complete code is available on github.

FacebooktwitterredditpinterestlinkedintumblrmailFacebooktwitterredditpinterestlinkedintumblrmail

6 thoughts on “cs193p – Assignment #5 Task #6”

  1. I’ve tried to use this code to check against mine, but I can’t get it to download any of the data from the Flickr website. I have the Flickr API key in each of the files you’ve posted to GitHub, but all the program does is spin when it starts on the “Top Places” VC.

    Steps I’ve taken to squash the bug:
    1) Checked to ensure I’ve got my Flickr API key in the project.
    2) Reset the iOS Simulator
    3) Nuked cached versions of the program in the Simulator
    4) Nuked derived data in the Organizer

    Any other suggestions to getting your code to work in my XCode?

    1. Please note, that I wrote this particular code (assignment #5) in December 2013. When I managed to free some time to finish assignment #6 last month (June 2014), I had quite some troubles accessing Flickr. Sometimes it was not accessible at all. Then they changed the API. Most of the time it worked using https requests. The FlickrFetcher files provided by Stanford only use http URLs. Therefore you need to change all “http://” strings to “https://” in FlickrFetcher.m. However, it might be Flickr changed something else since then. Have a look at the FlickrFetcher and it’s results …

      1. Hi Martin,
        I’m trying to do the Demo of the Lecture 11. The problem I have seems to be pretty much the same as I will have in the Assigment 5.

        So I explain you :
        I downloaded the folder FlickerFetcher yesterday, and the file FlickerFetcher.m inside it seems to have been updated in May 2014.

        So the “https://” version is well there instead of “http//” version. Therefore I don’t change anything in it. But when I test the log as in the Lecture, I have an error. The log is: “NSURLConnection/CFURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813)”… When I looked in the doc, I found it would be “No root certificate for the certificate chain.” (if I well look in the right place, at the end of this page: https://developer.apple.com/documentation/security/secure_transport )

        Any idea to fix this SLL problem quickly ?

        Thanks

          1. Thanks Martin for your help, but unfortunately, the link Justeen provided is exactly the same as the one provided by the update file from Stanford that I mentioned above.

            Since, I look for, and found, like PJames (here: https://cs193p.m2m.at/cs193p-lecture-11-table-view-and-ipad-fall-2013-14/#comment-133875), that Flickr changed their API and now they use an authentification based on OAuth1.0A. For this authentification method, it seems that the key is well necessary but not sufficient.
            Now I’m going to look for a snipet of code which can solve this… If someone find before me, or know such code, I’m very interested !
            Thanks again Martin

          2. Finally, I tested it myself:

            1. I opened my old (unchanged) project in Xcode
            2. I added the key in the .h file.
            3. I changed all occurrences of “http://” to “https://” (I think it was four or five times)
            4. I rebuild and run the project
            5. … and it worked … without changing anything else, no OAuth, no additional changes in the query part of the URLs

Leave a Reply

Your email address will not be published.