【iOS】7.4 定位服务->3.1 地图框架MapKit 功能1:地图展示
本文并非最终版本,如果想要关注更新或更正的内容请关注文集,联系方式详见文末,如有疏忽和遗漏,欢迎指正。
本文相关目录:
================== 所属文集:【iOS】07 设备工具 ==================
7.4 定位服务->1.0 简介
7.4 定位服务->2.1.1 定位 - 官方框架CoreLocation: 请求用户授权
7.4 定位服务->2.1.2 定位 - 官方框架CoreLocation: CLLocationManager位置管理器
7.4 定位服务->2.1.3.1 定位 - 官方框架CoreLocation 功能1:地理定位
7.4 定位服务->2.1.3.2 定位 - 官方框架CoreLocation 功能2:地理编码和反地理编码
7.4 定位服务->2.1.3.3 定位 - 官方框架CoreLocation 功能3:区域监听
7.4 定位服务->2.1.4 定位 - 官方框架CoreLocation 案例:指南针效果
7.4 定位服务->2.2 定位 - locationManager框架
7.4 定位服务->3.1 地图框架MapKit 功能1:地图展示
7.4 定位服务->3.2 地图框架MapKit 功能2:路线规划(导航)
7.4 定位服务->3.3 地图框架MapKit 功能3:3D视图
7.4 定位服务->3.4 地图框架MapKit 功能4:地图截图
7.4 定位服务->3.5 地图框架MapKit 功能5:POI检索
================== 所属文集:【iOS】07 设备工具 ==================
地图框架 - MapKit目录:
本文目录:
1.0 地图简介
下面展示上图中的3种效果图:
2.0 使用步骤
3.0 地图的基本使用
3.1 地图显示类型
下面介绍下地图显示类型的分类:
3.2 地图控制项
3.3 地图显示项
3.4 地图显示用户位置
3.5 常见问题
3.6 测试环境
代码11:地图的基本使用 Demo
编译环境:Xcode 8.0
模拟器版本:iOS 10
Swift版本:3.0
配置 info.plist 文件
【OC 语言】
#import "ViewController.h"
#import <CoreLocation/CoreLocation.h>
#import <MapKit/MapKit.h>
@interface ViewController ()
@property(weak, nonatomic) IBOutlet MKMapView *mapView; // 地图view
@property(nonatomic, strong) CLLocationManager *locationM; // 位置管理器
@end
@implementation ViewController
#pragma mark - 懒加载
- (CLLocationManager *)locationM {
if (!_locationM) {
// 创建位置管理者
_locationM = [[CLLocationManager alloc] init];
// 请求用户授权
if ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0) {
// if ([_locationM
// respondsToSelector:@selector(requestAlwaysAuthorization)]) {
[_locationM requestAlwaysAuthorization];
}
}
return _locationM;
}
- (void)viewDidLoad {
[super viewDidLoad];
#pragma mark - 设置地图显示类型
self.mapView.mapType = MKMapTypeStandard;
#pragma mark - 设置地图的控制项
// 注意:设置对应的属性时,注意该属性是从哪个系统版本开始引入的,做好不同系统版本的适配
self.mapView.zoomEnabled = true; // 是否可以缩放
self.mapView.scrollEnabled = true; // 是否可以滚动
self.mapView.rotateEnabled = true; // 是否可以旋转
self.mapView.pitchEnabled = true; // 是否显示3D
#pragma mark - 设置地图显示项
self.mapView.showsCompass = true; // 是否显示指南针
self.mapView.showsScale = true; // 是否显示比例尺
self.mapView.showsPointsOfInterest = true; // 是否显示兴趣点(POI)
self.mapView.showsBuildings = true; // 是否显示建筑物
self.mapView.showsTraffic = true; // 是否显示交通
#pragma mark - 地图显示用户位置
[self locationM]; //调用懒加载,请求用户授权
// 显示用户位置方案1:(需要请求用户授权)
// 效果:显示一个蓝点,在地图上面标示用户的位置信息
// 缺点:不会自动放大地图,当用户位置移动时,地图不会自动跟着跑
self.mapView.showsUserLocation = true;
// 显示用户位置方案2:设置地图的跟随模式(需要请求用户授权)
// 效果:显示一个蓝点,在地图上面标示用户的位置信息,会自动放大地图,当用户位置移动时,地图会自动跟着跑
// 缺点:拖动地图后,地图不会再随着用户位置移动而移动
/*
MKUserTrackingModeNone = 0, // 不跟随
MKUserTrackingModeFollow, // 跟随用户位置
MKUserTrackingModeFollowWithHeading, // 跟随用户位置,并跟随用户方向
*/
self.mapView.userTrackingMode = MKUserTrackingModeFollowWithHeading;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
【Swift 语言】
import UIKit
import MapKit
class ViewController: UIViewController {
@IBOutlet weak var mapView: MKMapView! // 地图view
// MARK: - 懒加载
lazy var locationM: CLLocationManager = {
let locationM = CLLocationManager()
// 请求用户授权(判断设备的系统)(当前target为7.0)
if #available(iOS 8.0, *) {
locationM.requestAlwaysAuthorization()
}
return locationM
}()
override func viewDidLoad() {
super.viewDidLoad()
// MARK: - 设置地图显示类型
mapView.mapType = MKMapType.standard
// MARK: - 设置地图的控制项
// 注意:设置对应的属性时,注意该属性是从哪个系统版本开始引入的,做好不同系统版本的适配
mapView.isZoomEnabled = true // 是否可以缩放
mapView.isScrollEnabled = true // 是否可以滚动
mapView.isRotateEnabled = true // 是否可以旋转
mapView.isPitchEnabled = true // 是否显示3D
// MARK: - 设置地图显示项
mapView.showsPointsOfInterest = true // 是否显示兴趣点(POI)
mapView.showsBuildings = true // 是否显示建筑物
if #available(iOS 9.0, *) {// 下面的方法是iOS 9.0以后加入的
mapView.showsCompass = true // 是否显示指南针
mapView.showsScale = true // 是否显示比例尺
mapView.showsTraffic = true // 是否显示交通
}
// MARK: - 地图显示用户位置
_ = locationM //调用懒加载,请求用户授权
// 显示用户位置方案1:(需要请求用户授权)
// 效果:显示一个蓝点,在地图上面标示用户的位置信息
// 缺点:不会自动放大地图,当用户位置移动时,地图不会自动跟着跑
mapView.showsUserLocation = true
// 显示用户位置方案2:设置地图的跟随模式(需要请求用户授权)
// 效果:显示一个蓝点,在地图上面标示用户的位置信息,会自动放大地图,当用户位置移动时,地图会自动跟着跑
// 缺点:拖动地图后,地图不会再随着用户位置移动而移动
/*
case none // 不跟随
case follow // 跟随用户位置
case followWithHeading // 跟随用户位置,并跟随用户方向
*/
mapView.userTrackingMode = MKUserTrackingMode.followWithHeading
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
4.0 功能实现:模拟追踪显示用户位置
4.1 代码实现
步骤1:设置地图代理(略)
步骤2:实现代理方法
步骤3:MKUserLocation 大头针(数据)模型
代码13:大头针基本使用 Demo
编译环境:Xcode 8.0
模拟器版本:iOS 10
Swift版本:3.0
【OC 语言】
TDAnnotation.h
#pragma mark - 自定义大头针模型
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
@interface TDAnnotation : NSObject<MKAnnotation>
// 大头针所在经纬度(订在地图哪个位置)
@property (nonatomic, assign) CLLocationCoordinate2D coordinate;
// 大头针标注显示的标题
@property (nonatomic, copy, nullable) NSString *title;
// 大头针标注显示的子标题
@property (nonatomic, copy, nullable) NSString *subtitle;
@end
ViewController.m
#import "ViewController.h"
#import <MapKit/MapKit.h>
#import <CoreLocation/CoreLocation.h>
#import "TDAnnotation.h"
@interface ViewController ()<MKMapViewDelegate>
@property (weak, nonatomic) IBOutlet MKMapView *mapView;
@property (nonatomic, strong) CLLocationManager *locationM;
@end
@implementation ViewController
#pragma mark - 懒加载
-(CLLocationManager *)locationM{
if (!_locationM) {
_locationM = [[CLLocationManager alloc] init];
if ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0) {
[_locationM requestAlwaysAuthorization];
}
}
return _locationM;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self locationM];
// 设置用户位置跟踪模式
self.mapView.userTrackingMode = MKUserTrackingModeFollowWithHeading;
}
#pragma mark - 添加大头针
- (void)addAnnotationWithCoordinate:(CLLocationCoordinate2D)coordinate{
// 1、创建一个大头针数据模型
TDAnnotation *annotation = [[TDAnnotation alloc] init];
// 2、大头针数据模型的属性
annotation.coordinate = coordinate; //根据经纬度坐标添加大头针
// annotation.coordinate = self.customMapView.centerCoordinate;// 当前地图中心点对应的经纬度
annotation.title = @"大头针弹框的标题";
annotation.subtitle = @"大头针弹框的子标题";
// 3、添加大头针数据模型, 到地图上
[self.mapView addAnnotation:annotation];
}
#pragma mark - 移除地图上所有大头针
- (void)removeAllAnnotation{
// 1. 获取所有的大头针数据模型
NSArray *annotations = self.mapView.annotations;
// 2. 移除大头针
[self.mapView removeAnnotations:annotations];
}
#pragma mark - 触摸屏幕
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// 获取当前触摸点在地图上的坐标
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:self.mapView];
// 将坐标转换为经纬度
CLLocationCoordinate2D center = [self.mapView convertPoint:touchPoint toCoordinateFromView:self.mapView];
[self addAnnotationWithCoordinate:center];
}
#pragma mark - 触摸移动时,移除地图上所有大头针
-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self removeAllAnnotation];
}
#pragma mark - MKMapViewDelegate
-(void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation{
userLocation.title = @"用户位置的标题";
userLocation.subtitle = @"用户位置的标题";
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
运行效果:
【Swift 语言】
import UIKit
import MapKit
class ViewController: UIViewController {
@IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// 1、创建一个大头针数据模型
let annotation: TDAnnotation = TDAnnotation()
// 2、大头针数据模型的属性
annotation.coordinate = mapView.centerCoordinate
annotation.title = "大头针弹框的标题"
annotation.subtitle = "大头针弹框的子标题"
// 3、添加大头针数据模型, 到地图上
mapView.addAnnotation(annotation)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
// 1. 获取所有的大头针数据模型
let annotations = mapView.annotations
// 2. 移除大头针
mapView.removeAnnotations(annotations)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension ViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation) {
userLocation.title = "用户位置的标题"
userLocation.subtitle = "用户位置的标题"
}
}
代码14:大头针场景模拟 Demo
编译环境:Xcode 8.0
模拟器版本:iOS 10
Swift版本:3.0
【OC 语言】
TDAnnotation.h
#pragma mark - 自定义大头针模型
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
@interface TDAnnotation : NSObject<MKAnnotation>
// 大头针所在经纬度(订在地图哪个位置)
@property (nonatomic, assign) CLLocationCoordinate2D coordinate;
// 大头针标注显示的标题
@property (nonatomic, copy, nullable) NSString *title;
// 大头针标注显示的子标题
@property (nonatomic, copy, nullable) NSString *subtitle;
@end
ViewController.m
#import "ViewController.h"
#import <MapKit/MapKit.h>
#import <CoreLocation/CoreLocation.h>
#import "TDAnnotation.h"
@interface ViewController ()<MKMapViewDelegate>
@property (weak, nonatomic) IBOutlet MKMapView *mapView;
@property (nonatomic, strong) CLLocationManager *locationM;
@end
@implementation ViewController
#pragma mark - 懒加载
-(CLLocationManager *)locationM{
if (!_locationM) {
_locationM = [[CLLocationManager alloc] init];
if ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0) {
[_locationM requestAlwaysAuthorization];
}
}
return _locationM;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self locationM];
// 设置用户位置跟踪模式
self.mapView.userTrackingMode = MKUserTrackingModeFollowWithHeading;
}
#pragma mark - 添加大头针
- (void)addAnnotationWithCoordinate:(CLLocationCoordinate2D)coordinate{
// 1、创建一个大头针数据模型
TDAnnotation *annotation = [[TDAnnotation alloc] init];
// 2、大头针数据模型的属性
annotation.coordinate = coordinate; //根据经纬度坐标添加大头针
// annotation.coordinate = self.customMapView.centerCoordinate;// 当前地图中心点对应的经纬度
annotation.title = @"大头针弹框的标题";
annotation.subtitle = @"大头针弹框的子标题";
// 3、添加大头针数据模型, 到地图上
[self.mapView addAnnotation:annotation];
}
#pragma mark - 移除地图上所有大头针
- (void)removeAllAnnotation{
// 1. 获取所有的大头针数据模型
NSArray *annotations = self.mapView.annotations;
// 2. 移除大头针
[self.mapView removeAnnotations:annotations];
}
#pragma mark - 触摸屏幕
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// 获取当前触摸点在地图上的坐标
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:self.mapView];
// 将坐标转换为经纬度
CLLocationCoordinate2D center = [self.mapView convertPoint:touchPoint toCoordinateFromView:self.mapView];
[self addAnnotationWithCoordinate:center];
}
#pragma mark - 触摸移动时,移除地图上所有大头针
-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self removeAllAnnotation];
}
#pragma mark - MKMapViewDelegate
-(void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation{
userLocation.title = @"用户位置的标题";
userLocation.subtitle = @"用户位置的标题";
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
【Swift 语言】
TDAnnotation.swift
// 继承自NSObject,遵守MKAnnotation协议
import MapKit
class TDAnnotation: NSObject,MKAnnotation {
// 确定大头针扎在地图上哪个位置
var coordinate: CLLocationCoordinate2D
// 确定大头针弹框的标题
var title: String?
// 确定大头针弹框的子标题
var subtitle: String?
// 构造方法
init(coordinate:CLLocationCoordinate2D!, title:String?, subtitle:String?){
self.coordinate = coordinate
self.title = title
self.subtitle = subtitle
}
}
ViewController.swift
// 场景描述:鼠标点击在地图哪个位置, 就在对应的位置添加一个大头针, 并在标注弹框中显示对应的城市和街道;
import UIKit
import MapKit
class ViewController: UIViewController {
@IBOutlet weak var mapView: MKMapView!
//MARK: - 地理编码懒加载
lazy var geoCoder : CLGeocoder = CLGeocoder()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
//MARK: - 点击屏幕
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?){
// 1. 获取当前触摸点所在的位置
let point = touches.first?.location(in: mapView)
// 2. 将从mapView上获取的点转换对应的经纬度坐标
let coordinate = mapView.convert(point!, toCoordinateFrom: mapView)
// 3.1 根据经纬度创建大头针数据模型
let annotation = TDAnnotation(coordinate:coordinate, title: "大头针弹框的标题", subtitle: "大头针弹框的子标题")
// 4. 进行反地理编码
let location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
geoCoder.reverseGeocodeLocation(location) { (pls:[CLPlacemark]?, error:Error?) in
if error == nil {
let pl = pls?.first // 这里取第一个
print(pl)
// 赋值
annotation.title = pl?.locality
annotation.subtitle = pl?.name
}
}
// 3.2 添加大头针数据模型到地图
mapView.addAnnotation(annotation)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
相关类的属性:
MKAnnotationView(大头针控件)
MKPinAnnotationView(大头针系统对应的视图)
模拟系统默认的大头针实现方案
代理方法补充
代码15:自定义大头针 Demo
编译环境:Xcode 8.0
模拟器版本:iOS 10
Swift版本:3.0
【OC 语言】
TDAnnotation.h
#pragma mark - 自定义大头针模型
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
@interface TDAnnotation : NSObject<MKAnnotation>
// 大头针所在经纬度(订在地图哪个位置)
@property (nonatomic, assign) CLLocationCoordinate2D coordinate;
// 大头针标注显示的标题
@property (nonatomic, copy, nullable) NSString *title;
// 大头针标注显示的子标题
@property (nonatomic, copy, nullable) NSString *subtitle;
//自定义大头针图片
@property(nonatomic,copy, nullable) NSString *icon;
@end
ViewController.m
#import "ViewController.h"
#import <MapKit/MapKit.h>
#import <CoreLocation/CoreLocation.h>
#import "TDAnnotation.h"
@interface ViewController ()<MKMapViewDelegate>
@property (weak, nonatomic) IBOutlet MKMapView *mapView;
@property (nonatomic, strong) CLLocationManager *locationM;
@end
@implementation ViewController
#pragma mark - 懒加载
-(CLLocationManager *)locationM{
if (!_locationM) {
_locationM = [[CLLocationManager alloc] init];
if ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0) {
[_locationM requestAlwaysAuthorization];
}
}
return _locationM;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self locationM];
// 设置用户位置跟踪模式
self.mapView.userTrackingMode = MKUserTrackingModeFollowWithHeading;
}
#pragma mark - 添加大头针
- (void)addAnnotationWithCoordinate:(CLLocationCoordinate2D)coordinate{
// 1、创建一个大头针数据模型
TDAnnotation *annotation = [[TDAnnotation alloc] init];
// 2、大头针数据模型的属性
annotation.coordinate = coordinate; //根据经纬度坐标添加大头针
// annotation.coordinate = self.customMapView.centerCoordinate;// 当前地图中心点对应的经纬度
annotation.title = @"大头针弹框的标题";
annotation.subtitle = @"大头针弹框的子标题";
annotation.icon = @"1";
// 设置代理
self.mapView.delegate = self;
// 反地理编码:获取当前经纬度所在位置信息,用作大头针标注的标题和子标题
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
CLLocation *location =[[CLLocation alloc] initWithLatitude:coordinate.latitude
longitude:coordinate.longitude];
[geocoder reverseGeocodeLocation:location
completionHandler:^(NSArray<CLPlacemark *> *_Nullable placemarks, NSError *_Nullable error) {
if (error || placemarks.count == 0) {
return;
}
//获取地标信息
CLPlacemark *placemark = [placemarks firstObject];
annotation.title = placemark.locality;
annotation.subtitle = placemark.name;
}];
// 3、添加大头针数据模型, 到地图上
[self.mapView addAnnotation:annotation];
}
#pragma mark - 移除地图上所有大头针
- (void)removeAllAnnotation{
// 1. 获取所有的大头针数据模型
NSArray *annotations = self.mapView.annotations;
// 2. 移除大头针
[self.mapView removeAnnotations:annotations];
}
#pragma mark - 触摸屏幕
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// 获取当前触摸点在地图上的坐标
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:self.mapView];
// 将坐标转换为经纬度
CLLocationCoordinate2D center = [self.mapView convertPoint:touchPoint toCoordinateFromView:self.mapView];
[self addAnnotationWithCoordinate:center];
}
#pragma mark - 触摸移动时,移除地图上所有大头针
-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self removeAllAnnotation];
}
#pragma mark - MKMapViewDelegate
// 根据传进来的 viewForAnnotation 参数创建并返回对应的大头针控件
// 当添加大头针数据模型时,会调用此方法,获取对应的大头针视图。如果返回nil,则会显示系统默认的大头针视图。
// 系统默认的大头针视图对应的类 MKPinAnnotationView,大头针视图与tableview中的cell一样, 都使用“循环利用”的机制
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(TDAnnotation *)annotation{
NSLog(@"添加大头针数据模型时, 调用了这个方法");
// 判断annotation的类型,如果返回为空,代表大头针样式是由系统管理的 (即为光标样式)
if ([annotation isKindOfClass:[MKUserLocation class]]) {
NSLog(@"大头针样式是由系统管理的 (即为光标样式)");
return nil;
}
#pragma mark - 默认运行会显示案例1效果,注释案例1可以看到案例2的效果
// ================= 自定义方法案例1(模拟系统默认的大头针视图)
// 0. 从缓存池取出大头针视图
static NSString *ID1 = @"PinAnnotationView";
// 1. MKPinAnnotationView 有界面,默认不能显示图片
// 将 MKAnnotationView 类型转换为 MKPinAnnotationView
MKPinAnnotationView *PinAnnotationView = (MKPinAnnotationView *) [mapView dequeueReusableAnnotationViewWithIdentifier:ID1];
// 2. 如果为空,则创建
if (!PinAnnotationView) {
PinAnnotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation
reuseIdentifier:ID1];
}
// 3. 传递模型数据,重新赋值, 防止循环利用时, 产生的数据错乱
PinAnnotationView.annotation = annotation;
// 4. 设置大头针可以弹框
PinAnnotationView.canShowCallout = YES;
// 5. 设置大头针的颜色
if ([[UIDevice currentDevice].systemVersion floatValue] >= 9.0) {
// iOS9.0以后, 可以设置任意颜色 (MKAnnotationView没有此方法)
PinAnnotationView.pinTintColor = [UIColor blueColor] ;
}else{
// iOS 8.0的方法,只有3种颜色(MKAnnotationView没有此方法)
PinAnnotationView.pinColor = MKPinAnnotationColorPurple;
}
// 6. 设置大头针下落动画(MKAnnotationView没有此方法)
PinAnnotationView.animatesDrop = YES;
// 7. 设置大头针可以被拖拽(父类中的属性)
PinAnnotationView.draggable = YES;
return PinAnnotationView;
// ================= 自定义方法案例1(模拟系统默认的大头针视图)
// ================= 自定义方法案例2(自定义大头针)
// 0. 从缓存池取出大头针视图
static NSString *ID2 = @"annotationView";
// 1. MKAnnotationView 默认没有界面,可以显示图片
MKAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:ID2];
// 2. 如果为空,则创建
if (!annotationView) {
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation
reuseIdentifier:ID2];
}
// 4. 设置大头针图片
// 方法1:直接使用 MKAnnotationView 的 image 属性
annotationView.image = [UIImage imageNamed:@"2"];
// 方法2:使用自定义大头针属性
// annotationView.image = [UIImage imageNamed:annotation.icon];
// 5. 设置大头针可以弹出标注
annotationView.canShowCallout = YES;
// 5.1 设置标注左侧视图
UIImageView *leftIV = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 40, 40)];
leftIV.image = [UIImage imageNamed:@"huba.jpeg"];
annotationView.leftCalloutAccessoryView = leftIV;
// 5.2 设置标注右侧视图
UIImageView *rightIV = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 40, 40)];
rightIV.image = [UIImage imageNamed:@"htl.jpg"];
annotationView.rightCalloutAccessoryView = rightIV;
// 6. 设置下部弹框(详情视图),会把子标题覆盖
if ([[UIDevice currentDevice].systemVersion floatValue] >= 9.0) {
annotationView.detailCalloutAccessoryView = [[UISwitch alloc] init];
}
return annotationView;
// ================= 自定义方法案例2(自定义大头针)
}
// 当大头针马上添加到 mapView 时调用
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray<MKAnnotationView *> *)views {
NSLog(@"下面将改变大头针下落的速度");
for (MKAnnotationView *view in views) {
// if 条件的使用是为了不让光标类型的大头针有动画效果
// MKModernUserLocationView 是一个私有类; NSClassFromString 把字符串转换成一个 class 类型
if ([view isKindOfClass:NSClassFromString(@"MKModernUserLocationView")]) {
continue;
}
// 1.保存大头针的最终位置
CGRect viewFrame = view.frame;
// 2.改变大头针的位置
view.frame = CGRectMake(viewFrame.origin.x, 0, viewFrame.size.width,viewFrame.size.height);
// 3.动画回归最终的位置
[UIView animateWithDuration:0.25 animations:^{
view.frame = viewFrame;
}];
}
}
-(void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation{
userLocation.title = @"当前位置的标题";
userLocation.subtitle = @"当前位置的子标题";
}
// 选中一个大头针时调用
-(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view{
NSLog(@"选中%@", [view.annotation title]);
}
// 取消选中大头针时调用
-(void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view{
NSLog(@"取消选中%@", [view.annotation title]);
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
运行效果在 swift 版本的代码后
【Swift 语言】
TDAnnotation.swift
// 继承自NSObject,遵守MKAnnotation协议
import MapKit
class TDAnnotation: NSObject,MKAnnotation {
// 确定大头针扎在地图上哪个位置
var coordinate: CLLocationCoordinate2D
// 确定大头针弹框的标题
var title: String?
// 确定大头针弹框的子标题
var subtitle: String?
// 构造方法
init(coordinate:CLLocationCoordinate2D!, title:String?, subtitle:String?){
self.coordinate = coordinate
self.title = title
self.subtitle = subtitle
}
}
ViewController.swift
import UIKit
import MapKit
class ViewController: UIViewController {
// MARK: - 地图懒加载
@IBOutlet weak var mapView: MKMapView!
// MARK: - 地理编码懒加载
lazy var geoCoder : CLGeocoder = CLGeocoder()
// MARK: - viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
}
//MARK: - 触摸屏幕,添加大头针数据模型
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// 1. 获取当前触摸点所在的位置
let point = touches.first?.location(in: mapView)
// 2. 把触摸点所在的位置, 转换成为在地图上的经纬度坐标
let coordinate = mapView.convert(point!, toCoordinateFrom: mapView)
// 3.1 创建大头针数据模型
// 方法1:使用系统大头针数据模型
// let annotation = MKUserLocation()
// 这个赋值操作会报错, 因为该属性是只读属性, 所以, 系统提供的大头针数据模型, 我们没法使用,只能自定义大头针数据模型
// annotation.location = CLLocation(latitude: mapView.centerCoordinate.latitude, longitude: mapView.centerCoordinate.longitude)
// 方法2:使用自定义大头针数据模型
let annotation = TDAnnotation(coordinate: coordinate, title: "大头针弹框的标题", subtitle: "大头针弹框的子标题")
// 4. 反地理编码
let location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
geoCoder.reverseGeocodeLocation(location) { (pls:[CLPlacemark]?, error:Error?) in
if error == nil
{
let pl = pls!.first
annotation.title = pl?.locality
annotation.subtitle = pl?.name
}
}
// 3.2 添加大头针数据模型到地图
mapView.addAnnotation(annotation)
}
//MARK: - 移除大头针数据模型
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
print("移除大头针数据模型时, 调用了这个方法")
mapView.removeAnnotations(mapView.annotations)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
// 代理方法
extension ViewController: MKMapViewDelegate {
// 根据传进来的 annotation 参数创建并返回对应的大头针控件
// 当添加大头针数据模型时,会调用此方法,获取对应的大头针视图。如果返回nil,则会显示系统默认的大头针视图。
// 系统默认的大头针视图对应的类 MKPinAnnotationView,大头针视图与tableview中的cell一样, 都使用“循环利用”的机制
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
print("添加大头针数据模型时, 调用了这个方法")
//MARK: - 默认运行会显示案例1效果,注释案例1可以看到案例2的效果
// ================= 自定义方法案例1(模拟系统默认的大头针视图)
// 0. 从缓存池取出大头针视图
let ID1 = "PinAnnotationView"
// 1. MKPinAnnotationView 有界面,默认不能显示图片
// 将 MKAnnotationView 类型转换为 MKPinAnnotationView
var PinAnnotationView:MKPinAnnotationView? = mapView.dequeueReusableAnnotationView(withIdentifier: ID1) as! MKPinAnnotationView?
// 2. 如果为空,则创建
if PinAnnotationView == nil{
PinAnnotationView = MKPinAnnotationView(annotation: annotation,
reuseIdentifier: ID1)
}
// 3. 传递模型数据,重新赋值, 防止循环利用时, 产生的数据错乱
PinAnnotationView?.annotation = annotation
// 4. 设置大头针可以弹框
PinAnnotationView?.canShowCallout = true
// 5. 设置大头针的颜色
if #available(iOS 9.0, *) {
// iOS9.0以后, 可以设置任意颜色(MKAnnotationView没有此方法)
PinAnnotationView?.pinTintColor = UIColor.black
} else {
// iOS 8.0的方法,只有3种颜色(MKAnnotationView没有此方法)
PinAnnotationView?.pinColor = MKPinAnnotationColor.green
}
// 6. 设置大头针下落动画(MKAnnotationView没有此方法)
PinAnnotationView?.animatesDrop = true
return PinAnnotationView
// ================= 自定义方法案例1(模拟系统默认的大头针视图)
// ================= 自定义方法案例2(自定义大头针)
// 1. 从缓存池取出大头针视图
let ID2 = "annotationView"
// MKAnnotationView 默认没有界面,可以显示图片
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: ID2)
// 2. 如果为空,则创建
if annotationView == nil{
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: ID2)
}
// 3. 重新赋值, 防止循环利用时, 产生的数据错乱
annotationView!.annotation = annotation
// 4. 设置大头针图片
annotationView!.image = UIImage(named: "1")
// 5. 设置大头针中心偏移量
annotationView?.centerOffset = CGPoint(x: 0, y: 0)
// 6. 设置可以弹框
annotationView!.canShowCallout = true
// 设置弹框的偏移量
annotationView?.calloutOffset = CGPoint(x: -10, y: 10)
// 6.1 设置弹框左侧视图
let leftIMV = UIImageView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
leftIMV.image = UIImage(named: "huba")
annotationView?.leftCalloutAccessoryView = leftIMV
// 6.2 设置弹框右侧视图
let rightIMV = UIImageView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
rightIMV.image = UIImage(named: "htl")
annotationView?.rightCalloutAccessoryView = rightIMV
// 6.3 设置下部弹框(详情视图),会把子标题覆盖
if #available(iOS 9.0, *) {
annotationView?.detailCalloutAccessoryView = UISwitch()
}
// 7. 设置大头针可以拖动
annotationView?.isDraggable = true
return annotationView
// ================= 自定义方法案例2(自定义大头针)
}
// MARK: - 选中一个大头针时调用的方法
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
print("选中了大头针---\(view.annotation?.title)")
}
// MARK: - 取消选中某个大头针时调用的方法
func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) {
print("取消选中了大头针---\(view.annotation?.title)")
}
}
运行效果:
步骤4:调整地图中心和显示区域
下面详细介绍下方案2:
步骤1:设置地图显示的中心
步骤2:设置跨度
步骤3:设置区域
步骤4:设置地图显示的区域
代码12:模拟追踪显示用户位置 Demo
编译环境:Xcode 8.0
模拟器版本:iOS 10
Swift版本:3.0
【OC 语言】
#import "ViewController.h"
#import <CoreLocation/CoreLocation.h>
#import <MapKit/MapKit.h>
@interface ViewController ()<MKMapViewDelegate>
@property (weak, nonatomic) IBOutlet MKMapView *mapView; // 地图view
@property(nonatomic, strong) CLLocationManager *locationM; // 位置管理器
@end
@implementation ViewController
#pragma mark - 懒加载
- (CLLocationManager *)locationM {
if (!_locationM) {
// 创建位置管理者
_locationM = [[CLLocationManager alloc] init];
// 请求用户授权
if ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0) {
[_locationM requestAlwaysAuthorization];
}
}
return _locationM;
}
#pragma mark - 地图显示用户位置
- (void)viewDidLoad {
[super viewDidLoad];
[self locationM]; //调用懒加载,请求用户授权
// 显示用户位置:跟踪模式
// 缺点:iOS8.0之前,地图不会自动滚到用户所在位置
// 解决方案:设置地图代理,在地图获取用户位置代理方法中操作;设置地图显示中心/设置地图显示区域
self.mapView.userTrackingMode = MKUserTrackingModeFollowWithHeading;
// 设置地图代理, 监听地图各个事件
self.mapView.delegate = self;
}
#pragma mark - 当地图更新用户位置信息时, 调用的方法
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation {
// 方案1:根据用户当前位置的经纬度,设置地图的中心,显示在当前用户所在的位置
// 效果:地图会自动移动到指定的位置坐标,并显示在地图中心
// 缺点:地图显示比例过大,无法调整,不会自动放大地图
// 解决:直接使用对应的调整地图“显示区域”的API
[mapView setCenterCoordinate:userLocation.coordinate animated:YES];
/* 地图视图显示,不会更改地图的比例,会以地图视图高度或宽度较小的那个为基准,按比例调整
MKCoordinateSpan 跨度解释
latitudeDelta:纬度跨度,因为南北纬各90度,所以此值的范围是(0-180);此值表示整个地图视图宽度,显示多大跨度
longitudeDelta:经度跨度,因为东西经各180度,所以此值范围是(0-360):此值表示整个地图视图高度,显示多大跨度
*/
// 方案2:设置地图显示区域
// ①使用地图的经纬度设置地图显示的中心
// 中国地图全貌(纬度范围:3°51′N至53°33′N)(经度范围:73°33′E至135°05′E)
CLLocationCoordinate2D center =CLLocationCoordinate2DMake(28, 104); // 使用地图的经纬度设置地图显示的中心
MKCoordinateSpan span = MKCoordinateSpanMake(50, 64); // 设置跨度
MKCoordinateRegion region = MKCoordinateRegionMake(center, span); //设置区域
[self.mapView setRegion:region animated:YES];
// ②使用区域中心设置地图显示的中心
CLLocationCoordinate2D center =self.mapView.region.center; // 使用区域中心设置地图显示的中心
MKCoordinateSpan span = MKCoordinateSpanMake(0.0219952102009202, 0.0160932558432023); // 设置跨度
MKCoordinateRegion region = MKCoordinateRegionMake(center, span); // 设置区域
[self.mapView setRegion:region animated:YES];
// ③使用当前用户的位置设置地图显示的中心
CLLocationCoordinate2D center = userLocation.coordinate;
MKCoordinateSpan span =MKCoordinateSpanMake(0.0219952102009202, 0.0160932558432023); // 设置跨度
MKCoordinateRegion region = MKCoordinateRegionMake(center, span); // 设置区域
[mapView setRegion:region animated:YES];
}
#pragma mark - 区域改变的时候调用
// 应用场景:在不知道经纬度跨度的合适值的时候,将地图放大到自己想要的跨度,然后再控制台复制打印出来的跨度
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
// 打印经度和纬度的跨度
NSLog(@"%f-%f", mapView.region.span.latitudeDelta,
mapView.region.span.longitudeDelta);
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
【Swift 语言】
import UIKit
import MapKit
class ViewController: UIViewController {
@IBOutlet weak var mapView: MKMapView!// 地图view
// 懒加载
lazy var locationM: CLLocationManager = {
let locationM = CLLocationManager()
// 请求用户授权(判断设备的系统)(当前target为7.0)
if #available(iOS 8.0, *) {
locationM.requestAlwaysAuthorization()
}
return locationM
}()
// 地图显示用户位置
override func viewDidLoad() {
super.viewDidLoad()
_ = locationM //调用懒加载,请求用户授权
// 显示用户位置:跟踪模式
// 缺点:iOS8.0之前,地图不会自动滚到用户所在位置
// 解决方案:设置地图代理,在地图获取用户位置代理方法中操作;设置地图显示中心/设置地图显示区域
mapView.userTrackingMode = MKUserTrackingMode.followWithHeading
mapView.delegate = self
}
}
extension ViewController: MKMapViewDelegate {
// 当地图更新用户位置信息时, 调用的方法
func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation) {
// MKUserLocation: 大头针数据模型
// location : 者就是大头针的位置信息(经纬度)
// heading: 设备朝向对象
// title: 弹框标题
// subtitle: 弹框子标题
userLocation.title = "大头针弹框的标题" // 大头针弹框的标题
userLocation.subtitle = "大头针弹框的子标题" // 大头针弹框的子标题
// 方案1:根据用户当前位置的经纬度,设置地图的中心,显示在当前用户所在的位置
// 效果:地图会自动移动到指定的位置坐标,并显示在地图中心
// 缺点:地图显示比例过大,无法调整,不会自动放大地图
// 解决:直接使用对应的调整地图“显示区域”的API
mapView.setCenter((userLocation.location?.coordinate)!, animated: true)
/* 地图视图显示,不会更改地图的比例,会以地图视图高度或宽度较小的那个为基准,按比例调整
MKCoordinateSpan 跨度解释
latitudeDelta:纬度跨度,因为南北纬各90度,所以此值的范围是(0-180);此值表示整个地图视图宽度,显示多大跨度
longitudeDelta:经度跨度,因为东西经各180度,所以此值范围是(0-360):此值表示整个地图视图高度,显示多大跨度
*/
// 方案2:设置地图显示区域
// ①使用地图的经纬度设置地图显示的中心
// 中国地图全貌(纬度范围:3°51′N至53°33′N)(经度范围:73°33′E至135°05′E)
let center = CLLocationCoordinate2DMake(28, 104) // 使用地图的经纬度设置地图显示的中心
let span = MKCoordinateSpanMake(50, 64) // 设置跨度
let region: MKCoordinateRegion = MKCoordinateRegionMake(center, span) // 设置区域
mapView.setRegion(region, animated: true)
// ②使用区域中心设置地图显示的中心
let center = (mapView.region.center) // 使用区域中心设置地图显示的中心
let span = MKCoordinateSpanMake(0.0219952102009202, 0.0160932558432023)// 设置跨度
let region: MKCoordinateRegion = MKCoordinateRegionMake(center, span)// 设置区域
mapView.setRegion(region, animated: true)
// ③使用当前用户的位置设置地图显示的中心
let center = (userLocation.location?.coordinate)!
let span = MKCoordinateSpanMake(0.0219952102009202, 0.0160932558432023)// 设置跨度
let region: MKCoordinateRegion = MKCoordinateRegionMake(center, span)// 设置区域
mapView.setRegion(region, animated: true)
}
// 区域改变的时候调用
// 应用场景:在不知道经纬度跨度的合适值的时候,将地图放大到自己想要的跨度,然后再控制台复制打印出来的跨度
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
// 打印经度和纬度的跨度
print(mapView.region.span.latitudeDelta, mapView.region.span.longitudeDelta)
}
}
4.2 常见问题
4.3 测试环境
---
本文源码 Demo 详见 Github
https://github.com/shorfng/iOS_7.0_Device-Tools
作者:蓝田(Loto)
【作品发布平台】
① 简书
② 博客园
③ Gitbook(如果觉得文章太长,请阅读此平台发布的文章)
【代码托管平台】
【如有疑问,请通过以下方式交流】
① 评论区回复
② 发送邮件
至 shorfng@126.com
本文版权归作者和本网站共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,谢谢合作。
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
-
支付宝扫一扫 向我打赏
-
你也可以微信 向我打赏