GeocoderDemo/CoordinateSelectorTableViewController.m

/*
 Copyright (C) 2014 Apple Inc. All Rights Reserved.
 See LICENSE.txt for this sample’s licensing information
 
 */
 
#import "CoordinateSelectorTableViewController.h"
 
// pull this in so we can use ABCreateStringWithAddressDictionary()
#import <AddressBookUI/AddressBookUI.h>
 
 
@interface CoordinateSelectorTableViewController ()
 
@property (nonatomic, strong) NSArray *searchPlacemarksCache;
 
@property (nonatomic, strong) CLLocationManager *locationManager;
 
@property (nonatomic, strong) NSIndexPath *checkedIndexPath;
 
@property (nonatomic, strong) IBOutlet UITableViewCell *searchCell;
@property (nonatomic, strong) IBOutlet UITextField *searchTextField;
@property (nonatomic, strong) IBOutlet UIActivityIndicatorView *searchSpinner;
 
@property (nonatomic, strong) IBOutlet UITableViewCell *currentLocationCell;
@property (nonatomic, strong) IBOutlet UILabel *currentLocationLabel;
@property (nonatomic, strong) IBOutlet UIActivityIndicatorView *currentLocationActivityIndicatorView;
 
@property (readonly) NSInteger selectedIndex;
 
@end
 
 
#pragma mark -
 
@implementation CoordinateSelectorTableViewController
 
- (instancetype)init
{
    self = [super initWithStyle:UITableViewStyleGrouped];
    if (self)
    {
        // do some default variables setup
        _selectedCoordinate = kCLLocationCoordinate2DInvalid;
        _selectedType = CoordinateSelectorLastSelectedTypeUndefined;
        [self updateSelectedName];
        [self updateSelectedCoordinate];
    }
    return self;
}
 
 
 
#pragma mark - Setup
 
- (void)loadNibCells
{
    // load our custom table view cells from our nib
    [[NSBundle mainBundle] loadNibNamed:@"CoordinateSelectorTableViewCells" 
                                  owner:self 
                                options:nil];
}
 
 
#pragma mark - View lifecycle
 
- (void)viewDidLoad
{
    [super viewDidLoad];
 
    self.title = @"Select a Place";
    self.clearsSelectionOnViewWillAppear = NO;
    [self loadNibCells];
}
 
- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    
    [self updateSelectedCoordinate];
    
    // stop updating, we don't care no more…
    if (_selectedType == CoordinateSelectorLastSelectedTypeCurrent)
    {
        [self stopUpdatingCurrentLocation];
    }
}
 
- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    
    // start updating, we might care again
    if (_selectedType == CoordinateSelectorLastSelectedTypeCurrent)
    {
        [self startUpdatingCurrentLocation];
    }    
}
 
 
#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 1 + [_searchPlacemarksCache count];
    }
    else
    {
        return 1;
    }
}
 
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil)
    {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
    }
        
    // configure the cell...
    NSInteger section = indexPath.section;
    if (section == 1)
    { 
        // Current location
        //
        // load the custom cell from the Nib
        cell = _currentLocationCell;
        //cell.selectionStyle = UITableViewCellSelectionStyleBlue;
        cell.selectionStyle = UITableViewCellSelectionStyleNone;
        
        if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied || 
            [CLLocationManager authorizationStatus] == kCLAuthorizationStatusRestricted)
        {
            _currentLocationLabel.text = @"Location Services Disabled";
            //cell.selectionStyle = UITableViewCellSelectionStyleNone;
        }
 
    }
    else if ((section) == 0)
    {
        // Search
        //
        if (indexPath.row == 0)
        {
            return _searchCell;
        }
        // otherwise display the list of results
        CLPlacemark *placemark = _searchPlacemarksCache[indexPath.row - 1];
        
        NSString *addressStr = ABCreateStringWithAddressDictionary(placemark.addressDictionary, NO);
        cell.textLabel.text = addressStr;
        
        CLLocationDegrees latitude = placemark.location.coordinate.latitude;
        CLLocationDegrees longitude = placemark.location.coordinate.longitude;
        cell.detailTextLabel.text = [NSString stringWithFormat:@"φ:%.4F, λ:%.4F", latitude, longitude];
    }
    
    // show a check next to the selected option / cell
    if ([_checkedIndexPath isEqual:indexPath])
    {
        cell.accessoryType = UITableViewCellAccessoryCheckmark;
    }
    else
    {
        cell.accessoryType = UITableViewCellAccessoryNone;
    }
    
    return cell;
}
 
 
#pragma mark - Table view delegate
 
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{    
    // set the selected type
    NSInteger section = indexPath.section;
    
    if (section == 1)
    {
        _selectedType = CoordinateSelectorLastSelectedTypeCurrent;   
    }
    else if ((section) == 0)
    {
        _selectedType = CoordinateSelectorLastSelectedTypeSearch;   
    }
    
    // deselect the cell
    [self.tableView cellForRowAtIndexPath:indexPath].selected = NO;
 
    // if this is the search cell itself do nothing
    if (_selectedType == CoordinateSelectorLastSelectedTypeSearch && indexPath.row == 0)
    {
        return;
    }
 
    // if location services are restricted do nothing
    if (_selectedType == CoordinateSelectorLastSelectedTypeCurrent)
    {
        if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied || 
            [CLLocationManager authorizationStatus] == kCLAuthorizationStatusRestricted)
        {
            return;
        }
    }
    
    // set the selected row index
    _selectedIndex = indexPath.row;
        
    // move the checkmark from the previous to the new cell
    [self.tableView cellForRowAtIndexPath:_checkedIndexPath].accessoryType = UITableViewCellAccessoryNone;   
    [self.tableView cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryCheckmark;
 
    // set this row to be checked on next reload
    if (_checkedIndexPath != indexPath)
    {
        _checkedIndexPath = indexPath;
    }
        
    // set the selected name based on the selected type
    [self updateSelectedName]; 
    
    // set the selected coordinates based on the selected type and index
    [self updateSelectedCoordinate];
    
    // if current location has been selected, start updating current location
    if (_selectedType == CoordinateSelectorLastSelectedTypeCurrent)
    {
        [self startUpdatingCurrentLocation];
    }
    
    // if regular or search, pop back to previous level
    if (_selectedType == CoordinateSelectorLastSelectedTypeSearch)
    {
        [self.navigationController popViewControllerAnimated:YES];
    }
}
 
 
#pragma mark - update selected cell
 
// keys off selectedType and selectedCoordinates 
- (void)updateSelectedName
{
    switch (_selectedType)
    {
        case CoordinateSelectorLastSelectedTypeCurrent:
        {
            _selectedName = @"Current Location";
            break;
        }
            
        case CoordinateSelectorLastSelectedTypeSearch:
        {
            CLPlacemark *placemark = _searchPlacemarksCache[_selectedIndex - 1]; // take into account the first 'search' cell
            _selectedName = ABCreateStringWithAddressDictionary(placemark.addressDictionary, NO);
            break;
        }
            
        case CoordinateSelectorLastSelectedTypeUndefined:
        {
            _selectedName = @"Select a Place";
            break;
        }
    }
}
 
// keys off selectedType and selectedCoordinates 
- (void)updateSelectedCoordinate
{
    switch (_selectedType)
    {
        case CoordinateSelectorLastSelectedTypeSearch:
        { 
            // allow for the selection of search results,
            // take into account the first 'search' cell
            CLPlacemark *placemark = _searchPlacemarksCache[_selectedIndex - 1];
            _selectedCoordinate = placemark.location.coordinate;
            break;
        }
            
        case CoordinateSelectorLastSelectedTypeUndefined:
            _selectedCoordinate = kCLLocationCoordinate2DInvalid;
            break;
            
        case CoordinateSelectorLastSelectedTypeCurrent:
            break; // no need to update for current location (CL delegate callback sets it)
    }
}
 
 
#pragma mark - current location
 
- (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];
    [_currentLocationActivityIndicatorView startAnimating];
}
 
- (void)stopUpdatingCurrentLocation
{
    [_locationManager stopUpdatingLocation];
    [_currentLocationActivityIndicatorView stopAnimating];
}
 
 
#pragma mark - CLLocationManagerDelegate - Location updates
 
- (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;
    }
    
    _selectedCoordinate = [newLocation coordinate];
    
    // update the current location cells detail label with these coords
    _currentLocationLabel.text = [NSString stringWithFormat:@"φ:%.4F, λ:%.4F", _selectedCoordinate.latitude, _selectedCoordinate.longitude];
    
    // after recieving a location, stop updating
    [self stopUpdatingCurrentLocation];
}
 
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
    NSLog(@"%@", error);
    
    // stop updating
    [self stopUpdatingCurrentLocation];
    
    // set selected location to invalid location
    _selectedType = CoordinateSelectorLastSelectedTypeUndefined;
    _selectedCoordinate = kCLLocationCoordinate2DInvalid;
    _selectedName = @"Select a Location";
    _currentLocationLabel.text = @"Error updating location";
    
    // remove the check from the current Location cell
    _currentLocationCell.accessoryType = UITableViewCellAccessoryNone;
    
    // show an alert
    UIAlertView *alert = [[UIAlertView alloc] init];
    alert.title = @"Error updating location";
    alert.message = [error localizedDescription];
    [alert addButtonWithTitle:@"OK"];
    [alert show];
}
 
// invoked when the authorization status changes for this application
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
{ }
 
 
#pragma mark - placemarks search
 
- (void)lockSearch:(BOOL)lock
{
    self.searchTextField.enabled = !lock;
    self.searchSpinner.hidden = !lock;
}
 
- (void)performPlacemarksSearch
{
    [self lockSearch:YES];
    
    // perform geocode
    CLGeocoder *geocoder = [[CLGeocoder alloc] init];
    
    [geocoder geocodeAddressString:self.searchTextField.text completionHandler:^(NSArray *placemarks, NSError *error) {
        // There is no guarantee that the CLGeocodeCompletionHandler will be invoked on the main thread.
        // So we use a dispatch_async(dispatch_get_main_queue(),^{}) call to ensure that UI updates are always
        // performed from the main thread.
        //
        dispatch_async(dispatch_get_main_queue(),^ {
            if (_checkedIndexPath.section == 0)
            {
                // clear any current selections if they are search result selections
                _checkedIndexPath = nil;
            }
            
            _searchPlacemarksCache = placemarks; // might be nil
            [[self tableView] reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationNone];
            [self lockSearch:NO];
            
            if (placemarks.count == 0)
            {
                // show an alert if no results were found
                UIAlertView *alert = [[UIAlertView alloc] init];
                alert.title = @"No places were found.";
                [alert addButtonWithTitle:@"OK"];
                [alert show];
            }
        });
    }];
}
 
 
#pragma mark - UITextFieldDelegate
 
// dismiss the keyboard for the textfields 
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
    [self.searchTextField resignFirstResponder];
   
    // initiate a search
    [self performPlacemarksSearch];
    
    return YES;
}
 
- (void)textFieldDidEndEditing:(UITextField *)textField
{
    [self updateSelectedCoordinate];
}
 
@end