Mapkit基本开发指南

MapKit是iOS自带的地图库。可以实现定位,地理编码,地图标注等功能。

一:查看是否导入MapKit.framework。 xcode5.1之后会自动导入
二:创建地图
创建地图对象: _mapView = [[MKMapView alloc]initWithFrame:self.view.bounds];

1.打开地图时的初始位置
    1.1 设置地图中心点center
    
1
CLLocationCoordinate2D center = CLLocationCoordinate2DMake(latitude, longitude);
1.2设置地图可视范围 数字越小,可现实范围越小
1
MKCoordinateSpan span = MKCoordinateSpanMake(53-3, 135-73);
1.3设置region= cente+span
1
2
3
4
 CLLocationCoordinate2D center2 = CLLocationCoordinate2DMake(22.54387,113.950339);
MKCoordinateSpan span2 = MKCoordinateSpanMake(0.1, 0.1);
MKCoordinateRegion region2 = MKCoordinateRegionMake(center2, span2);
[_mapView setRegion:region2 animated:YES];
2.地图类型
1
2
3
4
5
MKMapTypeStandard = 0,
MKMapTypeSatellite,
MKMapTypeHybrid

_mapView.mapType = MKMapTypeStandard;
3.设置地图上显示内容
1
2
3
showsPointsOfInterest
showsBuildings
showsTraffic

三: 获取当前点击位置的经纬度
3.1给地图对象添加手势:

1
2
3
//长按放置大头针,并获取当前点击位置的经纬度
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(whereAmI:)];
[_mapView addGestureRecognizer:longPress];

3.2创建地图编码器 CLGeocoder——地图编码器分为正向编码和反向编码。两种需要创建两个编码对象,不能公用。
_coder = [[CLGeocoder alloc]init];

3.2.1正向地理编码::将中文地址–>经纬度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    [_coder geocodeAddressString:name completionHandler:^(NSArray *placemarks, NSError *error) {
NSLog(@"Error:%@",[error localizedDescription]);
NSLog(@"------------正向地理编码------------");
for (CLPlacemark * mark in placemarks) {
CLLocation *location = mark.location;//是对CLLocationCoordinate2D的封装
NSLog(@"(纬度:%f,经度:%f)",location.coordinate.latitude,location.coordinate.longitude);
NSLog(@"%@",mark.name);//位置的完整名称
NSLog(@"%@",mark.thoroughfare);//位置名称
NSLog(@"%@",mark.locality);//城市
NSLog(@"%@",mark.subLocality);//区
NSLog(@"%@",mark.administrativeArea);//省

//1.添加标注(大头针)
// MKPointAnnotation *ann = [[MKPointAnnotation alloc]init];
// //2.设置标注位置
// ann.coordinate = CLLocationCoordinate2DMake(location.coordinate.latitude,location.coordinate.longitude);
// ann.title = mark.thoroughfare;
// ann.subtitle = mark.name;

/*也可以使用自定义的Annotation*/
HLAnnotation *ann = [[HLAnnotation alloc]initWithTitle:mark.thoroughfare subtitle:mark.name coordinate:CLLocationCoordinate2DMake(location.coordinate.latitude, location.coordinate.longitude)];
//3.将标注添加到地图上
[_mapView addAnnotation:ann];
}
}];

3.2.2 反向地理编码:将经纬度—》中文地址信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[_coder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) {
NSLog(@"Error:%@",[error localizedDescription]);
NSLog(@"------------反向地理编码------------");
for (CLPlacemark * mark in placemarks) {
CLLocation *location = mark.location;//是对CLLocationCoordinate2D的封装
NSLog(@"(纬度:%f,经度:%f)",location.coordinate.latitude,location.coordinate.longitude);
NSLog(@"%@",mark.name);//位置的完整名称
NSLog(@"%@",mark.thoroughfare);//位置名称
NSLog(@"%@",mark.locality);//城市
NSLog(@"%@",mark.subLocality);//区
NSLog(@"%@",mark.administrativeArea);//省

//1.添加标注(大头针)
MKPointAnnotation *ann = [[MKPointAnnotation alloc]init];
//2.设置标注位置
ann.coordinate = CLLocationCoordinate2DMake(location.coordinate.latitude,location.coordinate.longitude);
ann.title = mark.thoroughfare;
ann.subtitle = mark.name;
//3.将标注添加到地图上
[_mapView addAnnotation:ann];

}
}];

3.3 获取当前点击位置——-主要方法是 [tap locationInView:_mapView];获取当前点击的位置

1
2
3
4
5
6
7
8
9
10
11
#pragma mark - 点击地图,添加标注大头针
- (void)whereAmI:(UITapGestureRecognizer *)tap{
//1.获取点击点的位置
CGPoint point = [tap locationInView:_mapView];
//将点位置转换为当前经纬度
CLLocationCoordinate2D locationCor = [_mapView convertPoint:point toCoordinateFromView:_mapView];

//NSLog(@"(纬度:%f,经度:%f)",locationCor.latitude,locationCor.longitude);
CLLocation *location = [[CLLocation alloc]initWithLatitude:locationCor.latitude longitude:locationCor.longitude];
[self reverseGeocodeLocation:location];
}

4.设置大头针的样式和状态——-需要遵守MKMapViewDelegate协议

1
2
3
4
5
#pragma  mark - MKMapViewDelegate
//也是有复用机制
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation;
//设置大头针的状态——重点两个:1.设置大头针(pin)的拖拽状态为end。2.更新大头针的标注(annotation)内容
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view didChangeDragState:(MKAnnotationViewDragState)newState fromOldState:(MKAnnotationViewDragState)oldState;

//具体代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation{

NSLog(@"annotation的类型:%@",[annotation class]);

//1.先从复用队列中取出可用的大头针,如果没有,则创建
MKAnnotationView *annView = [mapView dequeueReusableAnnotationViewWithIdentifier:@"ann"];
//判断一下当前annotation的数据类型,如果是用户定位MKUserLocation类,则直接返回,这样当前位置的view就是一个蓝色小圆圈,而不是大头针。
if ([annotation isKindOfClass:[MKUserLocation class]])
{
return annView;
}
if (annView == nil) {

annView = [[MKPinAnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:@"ann"];
}
//MKPinAnnotationView是MKAnnotationView的子类
//如果使用 annView = [MKPinAnnotationView alloc]
MKPinAnnotationView *pin = (MKPinAnnotationView *)annView;
//pin.pinColor = MKPinAnnotationColorGreen;
pin.animatesDrop = YES;

//2.设置大头针
//2.1设置大头针图片
//annView.image = [UIImage imageNamed:@"map"];
//2.2设置拖拽
annView.draggable = YES;
//2.3设置弹出泡泡
annView.canShowCallout = YES;
//2.4设置左右视图

UIButton * right = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
right.frame = CGRectMake(0, 0, 30, 30);
annView.rightCalloutAccessoryView = right;
// annView.leftCalloutAccessoryView
return annView;
}

//设置大头针的状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view didChangeDragState:(MKAnnotationViewDragState)newState fromOldState:(MKAnnotationViewDragState)oldState{
if (newState == MKAnnotationViewDragStateEnding) {
//结束大头针View的拖拽状态
[view setDragState:MKAnnotationViewDragStateNone animated:YES];
//更新当前大头针的标注内容
HLAnnotation *ann = view.annotation;
NSLog(@"(%f,%f)",ann.coordinate.latitude,ann.coordinate.longitude);
CLLocation *location = [[CLLocation alloc]initWithLatitude:ann.coordinate.latitude longitude:ann.coordinate.longitude];
//反向获取位置信息
[_coder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) {
NSLog(@"Error:%@",[error localizedDescription]);
NSLog(@"------------反向地理编码------------");
for (CLPlacemark * mark in placemarks) {
CLLocation *location = mark.location;//是对CLLocationCoordinate2D的封装

//2.设置标注位置
ann.coordinate = CLLocationCoordinate2DMake(location.coordinate.latitude,location.coordinate.longitude);
[ann setTitle:mark.thoroughfare];
[ann setSubtitle:mark.name];
//3.将标注添加到地图上
[_mapView addAnnotation:ann];
}
}];
}
}

四:定位
用户定位与位置相关,所以使用的是CLLocationManager类。
1.打开用户位置显示开关
2.创建位置管理对象
3.设置精度和更新频率
4.启动位置更新

1
2
3
4
5
6
7
8
9
10
//创建位置管理对象
_manager = [[CLLocationManager alloc]init];
_mapView.showsUserLocation = YES;//显示用户当前位置
//设置精度和更新频率
_manager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;//精度
_manager.distanceFilter = 100.0;//更新频率,每经过100m更新一次

//启动定位,用完应该马上停止,定位结果在代理方法中获取
_manager.delegate = self;
[_manager startUpdatingLocation];

那么如何让地图自动跳转到定位的位置呢?这里需要实现协议CLLocationManagerDelegate。定位结果就是从协议方法中取得

1
2
3
4
5
6
7
8
9
10
11
12
#pragma mark - CLLocationManagerDelegate
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
CLLocation *currentLocation = [locations lastObject];
//将地图移动到当前定位的位置
CLLocationCoordinate2D currentCoordinate = currentLocation.coordinate;
MKCoordinateSpan span = _mapView.region.span;//当前地图缩放比例
MKCoordinateRegion region = {currentCoordinate,span};
[_mapView setRegion:region];

NSLog(@"定位结束%@",currentLocation);

}

五:多个经纬度做反向地理编码。

有个需求要求我们先把策划给的一张包含2664个城市经纬度的表,通过反向地理编码得出对应的城市名,在做的过程中遇到并处理了以下问题:
流程:
1.读取CVS文件,并写入数组中。
2.每次从数组中拿出50个数据,并判断是否finish。
3.创建定时器timer,实现一直循环第2步。
4.每次反向编码后的城市名写入文件中。

由于reverseGeocodeLocation处理经纬度的请求有限制:
reverseGeocodeLocation方法有如下描述

Submits a reverse-geocoding request for the specified location.

This method submits the specified location data to the geocoding server asynchronously and returns. When the request completes, the geocoder executes the provided completion handler on the main thread.
After initiating a reverse-geocoding request, do not attempt to initiate another reverse- or forward-geocoding request. Geocoding requests are rate-limited for each app, so making too many requests in a short period of time may cause some of the requests to fail. When the maximum rate is exceeded, the geocoder passes an error object with the value kCLErrorNetwork to your completion handler.

划重点:

  1. 这个方法是异步的。
  2. 同一个app在一段时间间隔内会有一个调用频率(rate-limited)。经测试, geocoding server一次可以处理的请求数为50个,并且时间间隔大约在1分钟左右。

针对以上两点,我采用了dispatch_group,每一个请求结束后leave group,并且notify到主线程,将结果写入另外的文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
- (void)geocodingForLocations:(NSArray *)locations {
__weak typeof(self)wself = self;
dispatch_group_t group = dispatch_group_create();
[locations enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
CLLocation *location = obj;

dispatch_group_enter(group);

CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder reverseGeocodeLocation:location completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
for (CLPlacemark * placemark in placemarks) {
NSLog(@"location:%@",location);
NSLog(@"name:%@",placemark.name);
NSDecimalNumberHandler *handle = [[NSDecimalNumberHandler alloc] initWithRoundingMode:NSRoundDown scale:6 raiseOnExactness:NO raiseOnOverflow:NO raiseOnUnderflow:NO raiseOnDivideByZero:NO] ;
NSDecimalNumber *rLan = [[NSDecimalNumber alloc] initWithDouble:location.coordinate.latitude];
NSDecimalNumber *rLon = [[NSDecimalNumber alloc] initWithDouble:location.coordinate.longitude];
rLan = [rLan decimalNumberByRoundingAccordingToBehavior:handle];
rLon = [rLon decimalNumberByRoundingAccordingToBehavior:handle];

NSString *llllan = [NSString stringWithFormat:@"%0.6f",location.coordinate.latitude];
NSString *llllon = [NSString stringWithFormat:@"%0.6f",location.coordinate.longitude];

NSString *city = placemark.locality ?placemark.locality: placemark.administrativeArea;
NSLog(@"city:%@",city);


BOOL same = NO;
NSString *tempNameFromGoogle = [NSString stringWithFormat:@"%@-%@",rLan,rLon];
//这里很耗时
for (NSDictionary *dic in self.locationsOfGoogle) {
NSString *latitude = dic[@"lan"];
NSString *longtitude = dic[@"lon"];
NSString *cityName = dic[@"name"];

NSComparisonResult latiresult = [rLan compare:latitude];
// NSComparisonResult lonresult = [rLan compare:latitude];

if (([latitude isEqualToString:llllan] && [longtitude isEqualToString:llllon]) || [cityName isEqualToString:city]){
//找到对应的坐标判断城市名
same = [cityName isEqualToString:city];
NSLog(@"城市名是否相同:%d--%f,%f",same,latitude.doubleValue,longtitude.doubleValue);
tempNameFromGoogle = cityName;
break;
}
}

[wself.citys appendString:[NSString stringWithFormat:@"%f,%f,%@,%d,%@\r\n",location.coordinate.latitude,location.coordinate.longitude, city,same,tempNameFromGoogle]];
}
NSLog(@"第%lu次 leave : %lu",wself.k/50,idx);

dispatch_group_leave(group);

}];
}];

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"第%lu次 notify",wself.k/50);
[self createFile];
});
}

六: MKMapView的zoomLevel
系统类没有直接获取zoomLevel的属性,参考一下
https://stackoverflow.com/questions/4189621/setting-the-zoom-level-for-a-mkmapview