/* |
Copyright (C) 2014 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
|
*/ |
|
#import "ForwardViewController.h" |
#import "PlacemarksListViewController.h" |
|
@interface ForwardViewController () <UITextFieldDelegate, |
UITableViewDelegate, |
UITableViewDataSource, |
CLLocationManagerDelegate> |
|
@property (nonatomic, strong) CLLocationManager *locationManager; // location manager for current location |
|
@property (nonatomic, strong) IBOutlet UITableViewCell *searchStringCell; |
@property (nonatomic, strong) IBOutlet UITextField *searchStringTextField; |
|
@property (nonatomic, strong) IBOutlet UISwitch *searchHintSwitch; |
|
@property (nonatomic, strong) IBOutlet UITableViewCell *searchRadiusCell; |
@property (nonatomic, strong) IBOutlet UILabel *searchRadiusLabel; |
@property (nonatomic, strong) IBOutlet UISlider *searchRadiusSlider; |
|
@property (readonly) CLLocationCoordinate2D selectedCoordinate; |
|
@property (weak, readonly) UIActivityIndicatorView *spinner; |
@property (weak, readonly) UIActivityIndicatorView *currentLocationActivityIndicatorView; |
|
@end |
|
|
#pragma mark - |
|
@implementation ForwardViewController |
|
- (void)viewDidLoad |
{ |
[super viewDidLoad]; |
|
_selectedCoordinate = kCLLocationCoordinate2DInvalid; |
|
// load our custom table view cells from our nib |
[[NSBundle mainBundle] loadNibNamed:@"ForwardViewControllerCells" |
owner:self |
options:nil]; |
|
#if 0 |
// forward geocode request using NSDictionary - |
// |
// geocodeAddressDictionary:completionHandler: takes an address dictionary as defined by the AddressBook framework. |
// You can obtain an address dictionary from an ABPerson by retrieving the kABPersonAddressProperty property. |
// Alternately, one can be constructed using the kABPersonAddress* keys defined in <AddressBook/ABPerson.h>. |
// |
NSDictionary *locationDictionary = [NSDictionary dictionaryWithObjectsAndKeys: |
@"San Francisco", kABPersonAddressCityKey, |
@"United States", kABPersonAddressCountryKey, |
@"us", kABPersonAddressCountryCodeKey, |
@"1398 Haight Street", kABPersonAddressStreetKey, |
@"94117", kABPersonAddressZIPKey, |
nil]; |
CLGeocoder *geocoder = [[CLGeocoder alloc] init]; |
[geocoder geocodeAddressDictionary:locationDictionary completionHandler:^(NSArray *placemarks, NSError *error) |
{ }]; |
#endif |
} |
|
|
#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 |
{ |
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]; |
|
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:1]]; |
assert(cell); |
if (cell) |
{ |
cell.accessoryView = curLocSpinner; |
} |
|
_currentLocationActivityIndicatorView = curLocSpinner; // keep a weak ref around for later |
} |
|
[self showSpinner:_currentLocationActivityIndicatorView withShowState:show]; |
} |
|
- (void)showSpinner:(BOOL)show |
{ |
if (_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.searchHintSwitch.enabled = !lock; |
self.searchRadiusSlider.enabled = !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 3; |
} |
|
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section |
{ |
// return the number of rows in the section |
if (section == 1) |
return self.searchHintSwitch.on ? 3 : 1; |
return 1; |
} |
|
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath |
{ |
// ----- interface builder generated cells ----- |
// |
// search string cell |
if (indexPath.section == 0) |
{ |
return self.searchStringCell; |
} |
|
// search radius cell |
if (indexPath.section == 1 && indexPath.row == 2) |
{ |
return self.searchRadiusCell; |
} |
|
// ----- non interface builder generated cells ----- |
// |
// search hint cell |
if (indexPath.section == 1 && indexPath.row == 0) |
{ |
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"radiusToggleCell"]; |
if (!cell) |
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"radiusToggleCell"]; |
cell.selectionStyle = UITableViewCellSelectionStyleNone; |
|
_searchHintSwitch = [[UISwitch alloc] initWithFrame:CGRectZero]; |
[self.searchHintSwitch sizeToFit]; |
[self.searchHintSwitch addTarget:self action:@selector(hintSwitchChanged:) forControlEvents:UIControlEventTouchUpInside]; |
cell.accessoryView = self.searchHintSwitch; |
|
cell.textLabel.text = @"Include Hint Region"; |
return cell; |
} |
|
// current location cell |
if (indexPath.section == 1 && indexPath.row == 1) |
{ |
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"radiusCell"]; |
if (!cell) |
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"radiusCell"]; |
|
cell.textLabel.text = @"Current Location"; |
|
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied || |
[CLLocationManager authorizationStatus] == kCLAuthorizationStatusRestricted ) |
{ |
cell.detailTextLabel.text = @"Location Services Disabled"; |
} |
else |
{ |
cell.detailTextLabel.text = @"<unknown>"; |
} |
cell.selectionStyle = UITableViewCellSelectionStyleNone; |
return cell; |
} |
|
// basic cell |
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"basicCell"]; |
if (!cell) |
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"basicCell"]; |
|
// geocode button |
if (indexPath.section == 2 && indexPath.row == 0) |
{ |
cell.textLabel.text = @"Geocode String"; |
cell.textLabel.textAlignment = NSTextAlignmentCenter; |
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; |
return cell; |
} |
|
return cell; |
} |
|
|
#pragma mark - UITableViewDelegate |
|
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath |
{ |
[[tableView cellForRowAtIndexPath:indexPath] setSelected:NO]; |
|
if (indexPath.section == 2 && indexPath.row == 0) |
{ |
// perform the Geocode |
[self performStringGeocode:self]; |
} |
} |
|
|
#pragma mark - UITextFieldDelegate |
|
// dismiss the keyboard for the textfields |
- (BOOL)textFieldShouldReturn:(UITextField *)textField |
{ |
[textField resignFirstResponder]; |
return YES; |
} |
|
|
#pragma mark - UIScrollViewDelegate |
|
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView |
{ |
// dismiss the keyboard upon a scroll |
[self.searchStringTextField resignFirstResponder]; |
} |
|
|
#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; |
} |
|
_selectedCoordinate = [newLocation coordinate]; |
|
// update the current location cells detail label with these coords |
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:1]]; |
cell.detailTextLabel.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]; |
|
// since we got an error, set selected location to invalid location |
_selectedCoordinate = kCLLocationCoordinate2DInvalid; |
|
// show the error alert |
UIAlertView *alert = [[UIAlertView alloc] init]; |
alert.title = @"Error obtaining location"; |
alert.message = [error localizedDescription]; |
[alert addButtonWithTitle:@"OK"]; |
[alert show]; |
} |
|
|
#pragma mark - Actions |
|
- (IBAction)hintSwitchChanged:(id)sender |
{ |
// show or hide the region hint cells |
NSArray *indexes = @[[NSIndexPath indexPathForRow:1 inSection:1], [NSIndexPath indexPathForRow:2 inSection:1]]; |
|
if (self.searchHintSwitch.on) |
{ |
[self.tableView insertRowsAtIndexPaths:indexes withRowAnimation:UITableViewRowAnimationAutomatic]; |
|
// start searching for our location coordinates |
[self startUpdatingCurrentLocation]; |
} |
else |
{ |
[self.tableView deleteRowsAtIndexPaths:indexes withRowAnimation:UITableViewRowAnimationAutomatic]; |
} |
} |
|
- (IBAction)radiusChanged:(id)sender |
{ |
self.searchRadiusLabel.text = [NSString stringWithFormat:@"%1.1f km", self.searchRadiusSlider.value/1000.0f]; |
} |
|
- (IBAction)performStringGeocode:(id)sender |
{ |
// dismiss the keyboard if it's currently open |
if ([self.searchStringTextField isFirstResponder]) |
{ |
[self.searchStringTextField resignFirstResponder]; |
} |
|
[self lockUI:YES]; |
|
CLGeocoder *geocoder = [[CLGeocoder alloc] init]; |
|
// if we are going to includer region hint |
if (self.searchHintSwitch.on) |
{ |
// use hint region |
CLLocationDistance dist = self.searchRadiusSlider.value; // 50,000m (50km) |
CLLocationCoordinate2D point = _selectedCoordinate; |
CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:point |
radius:dist |
identifier:@"Hint Region"]; |
|
[geocoder geocodeAddressString:self.searchStringTextField.text inRegion:region completionHandler:^(NSArray *placemarks, NSError *error) |
{ |
if (error){ |
NSLog(@"Geocode failed with error: %@", error); |
[self displayError:error]; |
return; |
} |
|
NSLog(@"Received placemarks: %@", placemarks); |
[self displayPlacemarks:placemarks]; |
}]; |
} |
else |
{ |
// don't use a hint region |
[geocoder geocodeAddressString:self.searchStringTextField.text 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 |