cs193p – Assignment #5 Task #3

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

Anywhere a place appears in a table view in your application, the most detailed part of the location (e.g. the city name) should be the title of the table view’s cell and the rest of the name of the location (e.g. state, province, etc.) should appear as the subtitle of the table view cell. The country will be in the section title.

In the Flickr helper class add a couple of new public instance methods which will handle the interface with Flickr:

+ (NSString *)countryOfPlace:(NSDictionary *)place;
+ (NSString *)titleOfPlace:(NSDictionary *)place;
+ (NSString *)subtitleOfPlace:(NSDictionary *)place;
+ (NSArray *)sortPlaces:(NSArray *)places;
+ (NSDictionary *)placesByCountries:(NSArray *)places;
+ (NSArray *)countriesFromPlacesByCountry:(NSDictionary *)placesByCountry;


To get the country name of a place create substrings of the place name (notice the individual parts are separated by a comma followed by a space) and use the last substring:

+ (NSString *)countryOfPlace:(NSDictionary *)place
{
    return [[[place valueForKeyPath:FLICKR_PLACE_NAME]
             componentsSeparatedByString:@", "] lastObject];
}

The title of a place equals the first substring of the place name:

+ (NSString *)titleOfPlace:(NSDictionary *)place
{
    return [[[place valueForKeyPath:FLICKR_PLACE_NAME]
             componentsSeparatedByString:@", "] firstObject];
}

To create a string containing everything but the title and the country, remove them and create a new substring from the rest:

+ (NSString *)subtitleOfPlace:(NSDictionary *)place
{
    NSArray *nameParts = [[place valueForKeyPath:FLICKR_PLACE_NAME]
                          componentsSeparatedByString:@", "];
    NSRange range;
    range.location = 1;
    range.length = [nameParts count] - 2;
    return [[nameParts subarrayWithRange:range] componentsJoinedByString:@", "];
}

The places need to be sorted by name, thus the place array needs to be sorted using a new comparison procedure to sort by the name part of a place:

+ (NSArray *)sortPlaces:(NSArray *)places
{
    return [places sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
        NSString *name1 = [obj1 valueForKeyPath:FLICKR_PLACE_NAME];
        NSString *name2 = [obj2 valueForKeyPath:FLICKR_PLACE_NAME];
        return [name1 localizedCompare:name2];
    }];
}

To divide the places into sections of their respective countries, its necessary to create a helper object providing those references. Loop over all places. Get the country name using the helper method from above. Get the places already found for that country. If there are none, initialize the new country. Add the current place. Finally return the result:

+ (NSDictionary *)placesByCountries:(NSArray *)places
{
    NSMutableDictionary *placesByCountry = [NSMutableDictionary dictionary];
    for (NSDictionary *place in places) {
        NSString *country = [FlickrHelper countryOfPlace:place];
        NSMutableArray *placesOfCountry = placesByCountry[country];
        if (!placesOfCountry) {
            placesOfCountry = [NSMutableArray array];
            placesByCountry[country] = placesOfCountry;
        }
        [placesOfCountry addObject:place];
    }
    return placesByCountry;
}

The last helper method returns an array of countries from the dictionary created by the previous helper method. First get the keys of that dictionary which equals the country names, then sort them:

+ (NSArray *)countriesFromPlacesByCountry:(NSDictionary *)placesByCountry
{
    NSArray *countries = [placesByCountry allKeys];
    countries = [countries sortedArrayUsingComparator:^(id a, id b) {
        return [a compare:b options:NSCaseInsensitiveSearch];
    }];
    return countries;
}

Create a new class as subclass from the table-view-controller class, which will be used as generic class for places table view controllers. Add a singe public property for storing an array of place dictionaries:

@property (nonatomic, strong) NSArray *places;

Add two private properties to hold an array of countries, and and dictionary to associate these countries with the places. Both of them are set using the helper methods from above, when the places property gets changed – in that case also do not forget to reload the table:

@property (nonatomic, strong) NSDictionary *placesByCountry;
@property (nonatomic, strong) NSArray *countries;
...
- (void)setPlaces:(NSArray *)places
{
    if (_places == places) return;    
    _places = [FlickrHelper sortPlaces:places];    
    self.placesByCountry = [FlickrHelper placesByCountries:_places];
    self.countries = [FlickrHelper countriesFromPlacesByCountry:self.placesByCountry];
    [self.tableView reloadData];
}

The number of sections equals the number of countries:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return [self.countries count];
}

The title of the sections are the names of those countries:

- (NSString *)tableView:(UITableView *)tableView
titleForHeaderInSection:(NSInteger)section
{
    return self.countries[section];
}

The number of rows in every sections, equals the number of places from the country of a section:

- (NSInteger)tableView:(UITableView *)tableView
 numberOfRowsInSection:(NSInteger)section
{
    return [self.placesByCountry[self.countries[section]] count];
}

Change the cell identifier suitable for places. Get the place for the cell (where the section equals the country and the row the position of the place of that country). … and generate the title and detail texts using the helper methods above:

- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Place Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier
                                                            forIndexPath:indexPath];    
    NSDictionary *place = self.placesByCountry[self.countries[indexPath.section]][indexPath.row];
    cell.textLabel.text = [FlickrHelper titleOfPlace:place];
    cell.detailTextLabel.text = [FlickrHelper subtitleOfPlace:place];    
    return cell;
}

Add another sub class of the just created class. Link this class to the table view controller in storyboard (don’t forget to set its cell identifier to the name you choose above). Use this new class to setup the places property. First start the refreshing cycle, then use the helper method from the last task to load the data from Flickr. On competition set the places property and stop the refresh cycle. Notice – like indicated in hint #16 – that the refresh control tends to stay hidden when fired by code. Thus scroll the table view upwards. Don’t forget to link the refresh control to this new action method:

- (IBAction)fetchPlaces
{
    [self.refreshControl beginRefreshing];
    [self.tableView setContentOffset:CGPointMake(0, -self.refreshControl.frame.size.height) animated:YES];    
    [FlickrHelper loadTopPlacesOnCompletion:^(NSArray *places, NSError *error) {
        if (!error) {
            self.places = places;
            [self.refreshControl endRefreshing];
        } else {
            NSLog(@"Error loading TopPlaces: %@", error);
        }
    }];
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self fetchPlaces];
}

The complete code is available on github.

FacebooktwitterredditpinterestlinkedintumblrmailFacebooktwitterredditpinterestlinkedintumblrmail

Leave a Reply

Your email address will not be published.