cs193p – Assignment #6 Part #8

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

Show Top Regions

Assignment #6 Task #1

Your application must work identically to last week except that where you are displaying the “top places” (as reported by Flickr), you are going to display the “top regions” in which photos have been taken. You will calculate the most popular regions from the data you gather periodically from the URLforRecentGeoreferencedPhotos.

Now it’s time to connect the database with the view of the app. Start by renaming the top-places view controllers. Select and right click the class name, choose Refactor -> Rename…. Change the name and keep “Rename related files” checked. This will affect the header and source file of that class as well as both story boards.

Repeat those steps with the place view controller.

Download the provided Core-Data-table-view controller from the Stanford course page, add it to the project and use it as parent class for the region-table-view controller.

In both story boards change all occurrences of “Top Places” to “Top Regions” and change the identifier of the top-table-view cell from “Place Cell” to “Region Cell”.

Remove the properties and methods from the region-table-view controller. For now it will have only a single method to populate the cells with the region name and data about the photographers and photos:

- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"Region Cell"];
    Region *region = [self.fetchedResultsController objectAtIndexPath:indexPath];
    cell.textLabel.text = region.name;
    cell.detailTextLabel.text = [NSString stringWithFormat:@"%@ photographers %@ photos", region.photographerCount, region.photoCount];
    return cell;
}

Clear out the top-regions-table view controller. The main task of this view controller is to connect to the database. However, currently only the application delegate knows about the managed-object context.

Use the photo-database-availability protocol from the lecture – or create a new one – defining a protocol with common constants:

#define PhotoDatabaseAvailabilityNotification @"PhotoDatabaseAvailabilityNotification"
#define PhotoDatabaseAvailabilityContext @"Context"
#define PhotoDatabaseNewDataAvailabilityNotification @"PhotoDatabaseNewDataAvailabilityNotification"

Adjust the application delegate to open the database document as soon as the app has launched, and post a notification when the document is ready:

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    ...
    [DocumentHelper useDocumentWithOperation:^(UIManagedDocument *document, BOOL success) {
        if (success) {
            NSDictionary *userInfo = document.managedObjectContext ? @{ PhotoDatabaseAvailabilityContext : document.managedObjectContext } : nil;
            [[NSNotificationCenter defaultCenter] postNotificationName:PhotoDatabaseAvailabilityNotification
                                                                object:self
                                                              userInfo:userInfo];
        }
    }];
    ...
}

Let the table-view controller listen for this notification:

- (void)awakeFromNib
{
    [super awakeFromNib];
    [[NSNotificationCenter defaultCenter] addObserverForName:PhotoDatabaseAvailabilityNotification
                                                      object:nil
                                                       queue:nil
                                                  usingBlock:^(NSNotification *note) {
                                                      self.managedObjectContext = note.userInfo[PhotoDatabaseAvailabilityContext];
                                                  }];
}

Assignment #6 Task #2

The popularity of a region is determined by how many different photographers have taken a photo in that region among the photos you’ve downloaded from Flickr. Only show the 50 most popular regions in your UI (it is okay if the table temporarily shows more than 50 as data is loaded into the database, but re-set it to 50 occasionally).

Assignment #6 Task #3

The list of top regions must be sorted first by popularity (most popular first, of course) and secondarily by the name of the region. Display the number of different photographers who have taken a photo in that region as a subtitle in each row.

When the managed-object context is set, setup also the fetched-results controller. Show only regions which have already got a name. Sort them by the number of photographers and the the name of the region, and limit the number of results:

@property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;
...
#define NUMBER_OF_SHOWN_REGIONS 50
- (void)setManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
{
    _managedObjectContext = managedObjectContext;    
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Region"];
    request.predicate = [NSPredicate predicateWithFormat:@"name.length > 0"];
    request.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"photographerCount"
                                                              ascending:NO
                                 ],[NSSortDescriptor
                                    sortDescriptorWithKey:@"name"
                                    ascending:YES
                                    selector:@selector(localizedCaseInsensitiveCompare:)]];
    request.fetchLimit = NUMBER_OF_SHOWN_REGIONS;    
    self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
                                                                        managedObjectContext:managedObjectContext
                                                                          sectionNameKeyPath:nil
                                                                                   cacheName:nil];
}

To reset the limit “occasionally” add another listener to detect when the database has been saved and then perform a refetch. Note, that we need only to listen to this notification while the view is on screen:

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(contextChanged:)
                                                 name:NSManagedObjectContextDidSaveNotification
                                               object:self.managedObjectContext];
}

- (void)viewWillDisappear:(BOOL)animated
{
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:NSManagedObjectContextDidSaveNotification
                                                  object:self.managedObjectContext];
    [super viewWillDisappear:animated];
}

 - (void)contextChanged:(NSNotification *)notification
{
    [self performFetch];
}

To see this code work, it’s a good idea to limit the number of regions to a smaller number, so that you can see all regions at once without scrolling (e.g. 5).

cs193p – assignment #6 task #1 - table populating with regions
table populating with regions

The complete code for tasks #1 to #7 is available on github.

FacebooktwitterredditpinterestlinkedintumblrmailFacebooktwitterredditpinterestlinkedintumblrmail

9 thoughts on “cs193p – Assignment #6 Part #8”

  1. I tested your code, the UI will freeze while scrolling and foreground fetching. Apparently, there is a conflit between scrolling and fetching.

    You can test it by setting FOREGROUND_FLICKR_FETCH_INTERVAL to a small value, like 5.

    1. At some point you need to return to the main queue to update the fetched data. Do you have the same problem using “normal” fetch intervals. I cannot recall that I have experienced it during my testing (a year ago). If you find out how to improve the code please provide your solution!

      1. What do you mean to return to the main queue to update the fetched data?

        We fetch the photos by using the method getTasksWithCompletionHandler of downloadSession. This is executed on the delegate queue (written in apple document, not in the main queue if I understand well.)

        Then we use useDocumentWithFlickrPhotos method to update the fetched data into document.

        So do you mean the useDocumentWithFlickrPhotos method is not in the main queue?

        1. The actual fetching process is not in the main queue, otherwise the app would be blocked … However, the completion handler will return to the main queue (afaik). Thus creating lots of download sessions will block the main queue, when the result is returned?

  2. The program crashes at [self performFetch] method in (void)contextChanged:(NSNotification *)notification.

    More precisely, it crashes at line “BOOL success = [self.fetchedResultsController performFetch:&error];
    ” of the performFetch method.

    It won’t crash if we replace [self performFetch] by the setter of fetchedResultsController. The noticed important difference between setFetchedResultsController and performFetch is:
    the former updates itself when its content changes and then assigns “self” to the new “fetchedResultsController”.delegate by “newfrc.delegate = self”.

    It works, but I don’t know why?

    1. Well, … add break points to your code. Start by checking if data gets loaded, if it is in the expected format, if your routines get called as you would expect them to do ……………….

Leave a Reply

Your email address will not be published.