I've a question about doing a local search in Apple maps which I have already implemented into my app. The app should show the user where are "supermarkets" around him. My mapView also shows the user's current location but I don't really know how I should do this with the results of the market-locations. Maybe Apple provides a solution/advice for this. Thanks in advance.
The results (supermarkets) should be displayed as in this picture.
UPDATE: The function works perfectly but is it also possible to make these pins clickable to show an info-sheet about this location as in the pictures below?
UPDATE 2: I'm so fascinated from this map-implemention, so I wonder if it's also possible to get infos about your current location (as in the screenshot below).
In iOS 6.1 and later, you can use MKLocalSearch, adding annotations for each of the MKMapItem objects you find.
MKLocalSearchRequest *request = [[MKLocalSearchRequest alloc] init];
request.naturalLanguageQuery = @"supermarket";
request.region = mapView.region;
MKLocalSearch *search = [[MKLocalSearch alloc] initWithRequest:request];
[search startWithCompletionHandler:^(MKLocalSearchResponse *response, NSError *error) {
for (MKMapItem *item in response.mapItems)
{
MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init];
annotation.coordinate = item.placemark.coordinate;
annotation.title = item.name;
annotation.subtitle = item.placemark.title;
[mapView addAnnotation:annotation];
}
}];
If you want the right and left accessories on your callouts, you should implement a viewForAnnotation that adds those accessories (and of course, for this to work, you have to define your controller to be the delegate for your MKMapView):
typedef enum : NSInteger
{
kCallOutAccessoryRight = 1,
kCallOutAccessoryLeft
} CallOutAccessoryType;
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
static NSString *identifier = @"customAnnotationView";
MKAnnotationView *annotationView = (MKAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (annotationView == nil)
{
annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
annotationView.canShowCallout = YES;
annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
annotationView.rightCalloutAccessoryView.tag = kCallOutAccessoryRight;
annotationView.leftCalloutAccessoryView = ...; // whatever you want for the left button
annotationView.leftCalloutAccessoryView.tag = kCallOutAccessoryLeft;
}
else
{
annotationView.annotation = annotation;
}
return annotationView;
}
And, you presumably want to respond to the user tapping on those callouts:
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
if (control.tag == kCallOutAccessoryRight)
{
NSLog(@"Present info sheet for %@ here", [view.annotation title]);
}
else if (control.tag == kCallOutAccessoryLeft)
{
NSLog(@"Do whatever you want if left accessory tapped");
}
}
You asked how to present the user directions. Yes, you can do that by passing it a MKMapItem to openMapsWithItems. But that presumes that you save the MKMapItem from the local search. To do this, you're creating a custom annotation. For example:
// MapItemAnnotation.h
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
@interface MapItemAnnotation : NSObject <MKAnnotation>
@property (nonatomic, strong, readonly) MKMapItem *item;
- (id)initWithMapItem:(MKMapItem *)item;
- (NSString *)title;
- (NSString *)subtitle;
- (CLLocationCoordinate2D)coordinate;
@end
And
// MapItemAnnotation.m
#import "MapItemAnnotation.h"
@implementation MapItemAnnotation
- (id)initWithMapItem:(MKMapItem *)item
{
self = [super init];
if (self) {
_item = item;
}
return self;
}
- (NSString *)title
{
return _item.name;
}
- (NSString *)subtitle
{
return _item.placemark.title;
}
- (CLLocationCoordinate2D)coordinate
{
return _item.placemark.coordinate;
}
@end
Having done that, your adding the annotation to your maps is simplified a bit:
[search startWithCompletionHandler:^(MKLocalSearchResponse *response, NSError *error) {
for (MKMapItem *item in response.mapItems)
{
MapItemAnnotation *annotation = [[MapItemAnnotation alloc] initWithMapItem:item];
[mapView addAnnotation:annotation];
}
}];
But, now, your left callout accessory can easily initiate directions in Apple Maps:
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
if (control.tag == kCallOutAccessoryRight)
{
NSLog(@"Present info sheet for %@ here", [view.annotation title]);
}
else if (control.tag == kCallOutAccessoryLeft)
{
// request directions from Apple Maps
MapItemAnnotation *annotation = view.annotation;
NSAssert([annotation isKindOfClass:[MapItemAnnotation class]], @"Annotation should be MapItemAnnotation: %@", annotation);
[annotation.item openInMapsWithLaunchOptions:@{MKLaunchOptionsMapCenterKey : [NSValue valueWithMKCoordinate:mapView.region.center],
MKLaunchOptionsMapSpanKey : [NSValue valueWithMKCoordinateSpan:mapView.region.span],
MKLaunchOptionsDirectionsModeKey : MKLaunchOptionsDirectionsModeDriving}];
}
}
Regarding updating the user location description, you can define a didUpdateUserLocation method (i.e. the map view calls this MKMapViewDelegate method every time the user's location changes). It looks like you want to update the subtitle for the user's location:
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
{
[self updateSubtitleForUserLocation:userLocation];
}
That would call this method that would do the reverse geocode and update the subtitle accordingly:
- (void)updateSubtitleForUserLocation:(MKUserLocation *)userLocation
{
if ([self.geocoder isGeocoding])
[self.geocoder cancelGeocode];
[self.geocoder reverseGeocodeLocation:userLocation.location completionHandler:^(NSArray *placemarks, NSError *error) {
MKPlacemark *placemark = placemarks[0];
userLocation.subtitle = placemark.name;
}];
}
Clearly, you need a class property:
@property (nonatomic, strong) CLGeocoder *geocoder;
And you need to instantiate that, e.g. viewDidLoad could:
self.geocoder = [[CLGeocoder alloc] init];
I might refine this a bit to only do a reverse geocode if the user's location has changed by more than x meters (you don't want to do too many reverse geocode requests; you'll kill the user's battery), but hopefully this illustrates the idea.
来源:https://stackoverflow.com/questions/17682834/mapview-with-local-search