GeocoderDemo/ReverseViewController.m

/*
 Copyright (C) 2014 Apple Inc. All Rights Reserved.
 See LICENSE.txt for this sample’s licensing information
 
 */
 
#import "ReverseViewController.h"
#import "PlacemarksListViewController.h"
 
#define kSanFranciscoCoordinate CLLocationCoordinate2DMake(37.776278, -122.419367)
 
@interface ReverseViewController () <UITableViewDelegate, UITableViewDataSource, CLLocationManagerDelegate>
 
@property (nonatomic, strong) CLLocationManager *locationManager;
 
@property (readonly) CLLocationCoordinate2D currentUserCoordinate;
@property (readonly) NSInteger selectedRow;
 
@property (weak, readonly) UIActivityIndicatorView *spinner;
@property (weak, readonly) UIActivityIndicatorView *currentLocationActivityIndicatorView;
 
@end
 
 
#pragma mark -
 
@implementation ReverseViewController
 
- (void)viewDidLoad
{
    [super viewDidLoad];
    
    // start with San Francisco
    _currentUserCoordinate = kCLLocationCoordinate2DInvalid;
    _selectedRow = 0;
}
 
 
#pragma mark - UI Handling
 
- (void)showSpinner:(UIActivityIndicatorView *)whichSpinner withShowState:(BOOL)show
{
    whichSpinner.hidden = (show) ? NO : YES;
    if (show)
    {
        [whichSpinner startAnimating];
    }
    else
    {
        [whichSpinner stopAnimating];
    }
}
 
- (void)showCurrentLocationSpinner:(BOOL)show
{
    UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]];
    assert(cell);
    
    if (!_currentLocationActivityIndicatorView)
    {
        // add the spinner to the table cell
        UIActivityIndicatorView *curLocSpinner =
            [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
        [curLocSpinner startAnimating];    
        [curLocSpinner setFrame:CGRectMake(200.0, 0.0, 22.0, 22.0)];
        [curLocSpinner setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin];
        
        _currentLocationActivityIndicatorView = curLocSpinner; // keep a weak ref around for later
        cell.accessoryView = _currentLocationActivityIndicatorView;
    }
    
    if (!show && _selectedRow == 1)
    {
        cell.accessoryView = nil;
        cell.accessoryType = UITableViewCellAccessoryCheckmark;
    }
}
 
- (void)showSpinner:(BOOL)show
{
    if (self.spinner == nil)
    {
        // add the spinner to the table's footer view
        UIView *containerView = [[UIView alloc] initWithFrame:
                                    CGRectMake(0.0, 0.0, CGRectGetWidth(self.tableView.frame), 22.0)];
        UIActivityIndicatorView *spinner =
            [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
           
        // size and center the spinner
        [spinner setFrame:CGRectZero];
        [spinner sizeToFit];
        CGRect frame = spinner.frame;
        frame.origin.x = (CGRectGetWidth(self.tableView.frame) - CGRectGetWidth(frame)) / 2.0;
        spinner.frame = frame;
        [spinner startAnimating]; 
        
        [containerView addSubview:spinner];
        self.tableView.tableFooterView = containerView;
        _spinner = spinner; // keep a weak ref around for later
    }
    
    [self showSpinner:self.spinner withShowState:show];
}
 
- (void)lockUI:(BOOL)lock
{
    // prevent user interaction while we are processing the forward geocoding
    self.tableView.allowsSelection = !lock;
    [self showSpinner:lock];
}
 
 
#pragma mark - Display Results
 
// display the results
- (void)displayPlacemarks:(NSArray *)placemarks
{
    dispatch_async(dispatch_get_main_queue(),^ {
        [self lockUI:NO];
        
        PlacemarksListViewController *plvc = [[PlacemarksListViewController alloc] initWithPlacemarks:placemarks preferCoord:YES];
        [self.navigationController pushViewController:plvc animated:YES];
    });
}
 
// display a given NSError in an UIAlertView
- (void)displayError:(NSError*)error
{
    dispatch_async(dispatch_get_main_queue(),^ {
        [self lockUI:NO];
 
        NSString *message;
        switch ([error code])
        {
            case kCLErrorGeocodeFoundNoResult: message = @"kCLErrorGeocodeFoundNoResult";
                break;
            case kCLErrorGeocodeCanceled: message = @"kCLErrorGeocodeCanceled";
                break;
            case kCLErrorGeocodeFoundPartialResult: message = @"kCLErrorGeocodeFoundNoResult";
                break;
            default: message = [error description];
                break;
        }
 
        UIAlertView *alert =  [[UIAlertView alloc] initWithTitle:@"An error occurred."
                                                    message:message
                                                   delegate:nil 
                                          cancelButtonTitle:@"OK"
                                           otherButtonTitles:nil];;
        [alert show];
    });   
}
 
 
#pragma mark - UITableViewDataSource
 
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    // return the number of sections
    return 2;
}
 
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // return the number of rows in the section
    if (section == 0)
        return 2;
    else
        return 1;
}
 
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
    if (section == 0)
        return @"Choose a location:";
    else
        return nil;
}
 
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"basicCell"];
    if (!cell)
    {
        UITableViewCellStyle style = (indexPath.section == 0) ? UITableViewCellStyleSubtitle : UITableViewCellStyleDefault; 
        cell = [[UITableViewCell alloc] initWithStyle:style reuseIdentifier:@"basicCell"];
    }
       
    if (indexPath.section == 0)
    {
        switch (indexPath.row)
        {
            case 0:
            {    
                cell.textLabel.text = @"San Francisco";
                cell.detailTextLabel.text = [NSString stringWithFormat:@"φ:%.4F, λ:%.4F", kSanFranciscoCoordinate.latitude, kSanFranciscoCoordinate.longitude];
                break;
            }
                
            case 1:
            {
                cell.textLabel.text = @"Current Location";
                if (CLLocationCoordinate2DIsValid(_currentUserCoordinate))
                    cell.detailTextLabel.text = [NSString stringWithFormat:@"φ:%.4F, λ:%.4F", _currentUserCoordinate.latitude, _currentUserCoordinate.longitude];
                else
                {
                    if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied || 
                        [CLLocationManager authorizationStatus] == kCLAuthorizationStatusRestricted )
                    {
                        cell.detailTextLabel.text = @"Location Services Disabled";
                    }
                    else
                    {
                        cell.detailTextLabel.text = @"<unknown>"; 
                    }
                }
                break;
            }
        }
        cell.selectionStyle = UITableViewCellSelectionStyleNone;
        
        UITableViewCellAccessoryType accessoryType =
            (_selectedRow == indexPath.row) ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone;
        cell.accessoryType = accessoryType;
    }
    else
    {
        cell.textLabel.text = @"Geocode Coordinate";
        cell.textLabel.textAlignment = NSTextAlignmentCenter;
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    }    
    
    return cell;
}
 
 
#pragma mark - UITableViewDelegate
 
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [[tableView cellForRowAtIndexPath:indexPath] setSelected:NO];
    
    if (indexPath.section == 1 && indexPath.row == 0)
    {
        // perform the Geocode
        [self performCoordinateGeocode:self];
    }
    else
    {
        NSInteger whichCellRow = (indexPath.row == 0) ? 1 : 0;
        
        UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:whichCellRow inSection:0]];
        cell.accessoryType = UITableViewCellAccessoryNone;
        
        cell = [self.tableView cellForRowAtIndexPath:indexPath];
        cell.accessoryType = UITableViewCellAccessoryCheckmark;
        
        if (indexPath.row == 1)
        {
            [self startUpdatingCurrentLocation];
        }
        
        _selectedRow = indexPath.row;
    }
}
 
 
#pragma mark - CLLocationManagerDelegate
 
- (void)startUpdatingCurrentLocation
{
    // if location services are restricted do nothing
    if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied || 
        [CLLocationManager authorizationStatus] == kCLAuthorizationStatusRestricted )
    {
        return;
    }
    
    // if locationManager does not currently exist, create it
    if (self.locationManager == nil)
    {
        _locationManager = [[CLLocationManager alloc] init];
        [self.locationManager setDelegate:self];
        self.locationManager.distanceFilter = 10.0f; // we don't need to be any more accurate than 10m
    }
    
    // for iOS 8, specific user level permission is required,
    // "when-in-use" authorization grants access to the user's location
    //
    // important: be sure to include NSLocationWhenInUseUsageDescription along with its
    // explanation string in your Info.plist or startUpdatingLocation will not work.
    //
    if ([_locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)])
    {
        [_locationManager requestWhenInUseAuthorization];
    }
    
    [_locationManager startUpdatingLocation];
    
    [self showCurrentLocationSpinner:YES];
}
 
- (void)stopUpdatingCurrentLocation
{
    [_locationManager stopUpdatingLocation];
    
    [self showCurrentLocationSpinner:NO];
}
 
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{       
    // if the location is older than 30s ignore
    if (fabs([newLocation.timestamp timeIntervalSinceDate:[NSDate date]]) > 30)
    {
        return;
    }
    
    _currentUserCoordinate = [newLocation coordinate];
    _selectedRow = 1;
    
    // update the current location cells detail label with these coords
    UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]];
    cell.detailTextLabel.text = [NSString stringWithFormat:@"φ:%.4F, λ:%.4F", _currentUserCoordinate.latitude, _currentUserCoordinate.longitude];
 
    // after recieving a location, stop updating
    [self stopUpdatingCurrentLocation];
}
 
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
    NSLog(@"%@", error);
    
    // stop updating
    [self stopUpdatingCurrentLocation];
    
    // since we got an error, set selected location to invalid location
    _currentUserCoordinate = kCLLocationCoordinate2DInvalid;
 
    // show the error alert
    UIAlertView *alert = [[UIAlertView alloc] init];
    alert.title = @"Error updating location";
    alert.message = [error localizedDescription];
    [alert addButtonWithTitle:@"OK"];
    [alert show];
}
 
 
#pragma mark - Actions
 
- (IBAction)performCoordinateGeocode:(id)sender
{
    [self lockUI:YES];
    
    CLGeocoder *geocoder = [[CLGeocoder alloc] init];
    
    CLLocationCoordinate2D coord = (_selectedRow == 0) ? kSanFranciscoCoordinate : _currentUserCoordinate;
    
    CLLocation *location = [[CLLocation alloc] initWithLatitude:coord.latitude longitude:coord.longitude];
    
    [geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) {
        if (error){
            NSLog(@"Geocode failed with error: %@", error);
            [self displayError:error];
            return;
        }
        NSLog(@"Received placemarks: %@", placemarks);
        [self displayPlacemarks:placemarks];
    }];
}
 
@end