地图 Part1 - 定位及大头针的基本使用
一.MapKit
- 作用 : 用于地图展示
- 如大头针,路线,覆盖层展示等(着重界面展示)
- 使用步骤
- 导入头文件
#import <MapKit/MapKit.h>
- MapKit有一个比较重要的UI控件
MKMapView
, 专门用来地图显示
二.地图的基本使用
0.首先在storyboard上添加一个地图控件 - MapKitView
- 连线控制器
@IBOutlet weak var mapView: MKMapView!
1.设置地图的类型
- 方法
// 可根据地图类型自己设定
mapView.mapType = .standard
- 地图的类型
@available(iOS 3.0, *)
public enum MKMapType : UInt {
case standard // 普通地图 (默认)
case satellite // 卫星云图
case hybrid // 混合地图(卫星云图+普通地图)
@available(iOS 9.0, *)
case satelliteFlyover // 3D卫星地图
@available(iOS 9.0, *)
case hybridFlyover // 3D混合卫星地图(3D卫星地图+普通地图)
}
2.设置地图的操作项
- false就是取消这些功能
// 缩放
mapView.isZoomEnabled = false
// 旋转
mapView.isRotateEnabled = false
// 滚动
mapView.isScrollEnabled = false
3.设置地图的显示项
// 设置地图显示项(3D卫星混合信息)
if #available(iOS 9.0, *) {
mapView.showsCompass = true // 指南针
mapView.showsTraffic = true // 交通
mapView.showsScale = true // 比例尺
}
// 设置地图显示项
mapView.showsBuildings = true // 建筑物
mapView.showsPointsOfInterest = true // 兴趣点
4.在iOS 8.0之后定位需要主动授权
- 懒加载位置管理者,请求授权写在里面
lazy var locationM : CLLocationManager = {
let locationM = CLLocationManager()
if #available(iOS 8.0, *) {
// 前后台授权
locationM.requestAlwaysAuthorization()
}
return locationM
}()
- 外界调用locationM的get方法,执行授权
定位,但不会追踪
_ = locationM
5.设置用户的追踪模式
有一个缺陷
- 只要动一下地图,就不再追踪用户的位置(不是很灵敏)
// 带方向的追踪
mapView.userTrackingMode = .followWithHeading
- 其他追踪模式
@available(iOS 5.0, *)
public enum MKUserTrackingMode : Int {
case none // 不追踪,也不会显示用户的位置(相当于showsUserLocation为false)
case follow // 追踪,会显示用户的位置showsUserLocation为true
case followWithHeading // 带方向的追踪,showsUserLocation为true
}
6.代理方法
- mapView设置代理
mapView.delegate = self
- 代理方法
6.1 当用户位置改变时
/// 当用户位置改变时就会来到这个方法
/// 在地图上显示一个蓝色的圆点来标注用户的位置
///
/// - Parameters:
/// - mapView: 地图视图
/// - userLocation: 大头针数据模型
func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation) {
// print("用户位置改变")
// 大头针的标题和子标题
userLocation.title = "我是标题?"
userLocation.subtitle = "我是子标题☺️"
// 设置用户的位置一直在地图的中心点
// 缺陷 : 默认情况下不会放大地图的显示区域,需要手动放大
let coordinate = userLocation.coordinate
mapView.setCenter(coordinate, animated: true)
}
- 方法区别
// 设置中心点时带动画效果
mapView.setCenter(coordinate, animated: true)
// 没有动画效果
mapView.centerCoordinate = coordinate
- userLocation - 大头针数据模型
@available(iOS 3.0, *)
open class MKUserLocation : NSObject, MKAnnotation {
// Returns YES if the user's location is being updated.
open var isUpdating: Bool { get }
// Returns nil if the owning MKMapView's showsUserLocation is NO or the user's location has yet to be determined.
open var location: CLLocation? { get }
// Returns nil if not in MKUserTrackingModeFollowWithHeading
@available(iOS 5.0, *)
open var heading: CLHeading? { get }
// The title to be displayed for the user location annotation.
open var title: String?
// The subtitle to be displayed for the user location annotation.
open var subtitle: String?
}
- 上面那种定位方法有缺陷:
- 默认情况下不会放大地图的显示区域,需要手动放大
改进缺陷的方法
: 可以直接放大地图的显示区域
extension ViewController : MKMapViewDelegate {
// 当用户位置改变时就会来到这个方法
func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation) {
// print("用户位置改变")
// 大头针的标题和子标题
userLocation.title = "我是标题?"
userLocation.subtitle = "我是子标题☺️"
// 设置用户的位置一直在地图的中心点
let coordinate = userLocation.coordinate
mapView.setCenter(coordinate, animated: true)
// span: 区域的跨度
// 在地图上,东西经各180°,显示的区域跨度为0~360°之间
// 南北纬各90°,显示的区域跨度为0~180°
// 结论: 区域跨度设置的越小,那么看到的内容就越清晰
let span = MKCoordinateSpan(latitudeDelta: 0.006, longitudeDelta: 0.004)
// region: 区域
// center: 地图的中心点(经度和纬度)
let region = MKCoordinateRegion(center: coordinate, span: span)
mapView.setRegion(region, animated: true)
/// 当区域改变时就会来到这个方法
/// 区域改变的条件: 1.地图中心点发生改变 || 2.跨度发生改变
///
/// - Parameters:
/// - mapView: 地图视图
/// - animated: 动画
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
print(mapView.region.span)
}
}
}
效果图
三.大头针的基本使用 - 添加/删除
1.理论支撑(按照MVC的原则)
- 在地图上操作大头针,实际上就是控制大头针的数据模型
- 添加大头针就是添加大头针的数据模型
- 删除大头针就是删除大头针的数据模型
2.添加/删除大头针到地图
- 1.
确定大头针的数据模型
,主要确定经纬度
,只有这样才能确定大头针在地图中的位置 - 2.之后可以确定title和subtitle
- 3.添加到地图上
方案一 [pass掉]
尝试使用系统的MKUserLocation创建大头针数据模型
- 因为
点进MKUserLocation
后发现,它里面有我们需要的信息,如location,title,subtitle等
open class MKUserLocation : NSObject, MKAnnotation {
open var isUpdating: Bool { get }
open var location: CLLocation? { get }
@available(iOS 5.0, *)
open var heading: CLHeading? { get }
open var title: String?
open var subtitle: String?
}
- 所以使用系统的MKUserLocation创建大头针的数据模型
// 创建大头针的数据模型
let annotation = MKUserLocation()
- 接着要设置大头针的经纬度为地图的中心位置
// mapView是storyboard拖线进来的,地图视图
annotation.coordinate = mapView.centerCoordinate [❌]
- 结果发现?的报错了,报错内容为
// 不允许给`coordinate`这个属性赋值,因为他是不可变的
Cannot assign to property" `coordinate` is immutable
// 从上面也可以看到MKUserLocation里面的location后面有个{get},说明是只读的,不允许我们去设置
- 结论: 所以我们借助系统来创建大头针无法满足我们的需求,我们要
自定义大头针了
方案二 自定义大头针
- 遵守MKAnnotation协议,实现它里面必须实现的方法(未被optional修饰的)
// MKAnnotation协议
public protocol MKAnnotation : NSObjectProtocol {
public var coordinate: CLLocationCoordinate2D { get }
optional public var title: String? { get }
optional public var subtitle: String? { get }
}
- 把这几个方法拿过来,去掉
{get}
,说明我们即实现了协议里面的方法,又为它新增了方法,set方法
class MTYAnnotation: NSObject , MKAnnotation {
// 必须赋初值
// 大头针的经纬度(在地图上显示的位置)
var coordinate: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: 0, longitude: 0)
// 大头针的标题
var title: String?
// 大头针的子标题
var subtitle: String?
}
代码实现
// 点击屏幕添加大头针
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// 要求: 点击屏幕,添加大头针
// 1.尝试使用MKUserLocation创建大头针
let annotation = MTYAnnotation()
// 2.设置大头针的位置
annotation.coordinate = mapView.centerCoordinate
// 3.设置标题
annotation.title = "我是标题?"
// 4.设置子标题
annotation.subtitle = "我是子标题☺️"
// 5.添加大头针到地图上
mapView.addAnnotation(annotation)
}
// 移除大头针
@IBAction func removeAnnotation(_ sender: UIBarButtonItem) {
// 1.获取需要移除的大头针
let annotations = mapView.annotations
// 2.移除大头针
mapView.removeAnnotations(annotations)
}
效果图
3.场景演练: 点击屏幕在对应位置添加大头针
- 步骤分析
- 首先点击屏幕,touchesBegan方法
- 要获取在控件上点击的点
- 将获取的点转为经纬度信息
- 创建大头针数据模型(标题和子标题必须设置占位文字)
- 使用自定义的大头针数据模型
- 标注弹框中显示的城市和街道
- 获取点的位置(经纬度信息)
- 反地理编码
- 判断是否有错误信息,有直接return
- 取出地标对象
- 更新标题和子标题
代码实现
// 点击屏幕添加大头针
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.创建大头针数据模型,并添加到地图上
// 标题,子标题必须设置占位文字
let annotation = addAnnotation(coordinate: coordinate, title: " ", subtitle: " ")
// 4.标注弹框中显示的城市和街道
// 4.1.获取点的位置(经纬度)
let location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
// 4.2.反地理编码
// completionHandler后的参数:
// clpls: 地标数组
// error: 错误信息
geoc.reverseGeocodeLocation(location, completionHandler: {clpls,error in
// 1.判断是否有错误
if error != nil {
return
}
// 2.取出地标对象
// 第一个相关度最高
guard let clpl = clpls?.first else { return }
// 3.更新标题和子标题
annotation.title = clpl.locality
annotation.subtitle = clpl.name
})
}
封装的添加大头针数据模型方法
// 添加大头针
func addAnnotation(coordinate: CLLocationCoordinate2D, title: String, subtitle: String) -> MTYAnnotation {
// 要求: 点击屏幕,添加大头针
// 1.尝试使用MKUserLocation创建大头针数据模型
let annotation = MTYAnnotation()
// 2.设置大头针的位置
annotation.coordinate = coordinate
// 3.设置标题
annotation.title = title
// 4.设置子标题
annotation.subtitle = subtitle
// 5.添加大头针到地图上
mapView.addAnnotation(annotation)
// 6.返回大头针数据模型
return annotation
}
移除大头针方法同上
效果图(联网!网速慢就比较尴尬...)
4.那么系统的大头针是如何添加到地图上的呢?
4.1 模拟系统的实现方案
- 缺点: 无法更改大头针的样式
// 这个方法不能设置大头针的样式
pinAnnotationView?.image =
代理方法
extension ViewController: MKMapViewDelegate
{
/// 系统的大头针是如何添加的?实现方法如下
/// 在地图上添加一个大头针数据模型时,系统就会自动调用这个代理方法,来设置对应的大头针视图
///
/// - Parameters:
/// - mapView: 地图视图
/// - annotation: 大头针数据模型
/// - Returns: 返回创建好的大头针视图
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
// 系统的大头针使用的是MKPinAnnotationView
let pinID = "pinID"
var pinAnnotationView = mapView.dequeueReusableAnnotationView(withIdentifier: pinID) as? MKPinAnnotationView
if pinAnnotationView == nil {
pinAnnotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: pinID)
}
// 更新数据模型,防止循环引用时使用之前的模型
pinAnnotationView?.annotation = annotation
// 设置允许弹框(自己写的时候,系统默认为false了)
pinAnnotationView?.canShowCallout = true
// 设置大头针的颜色
pinAnnotationView?.pinTintColor = UIColor.blue
// 设置大头针降落动画
pinAnnotationView?.animatesDrop = true
// 设置大头针可以被拖拽
pinAnnotationView?.isDraggable = true
return pinAnnotationView
}
/// 当拖拽大头针的时候来到此方法
///
/// - Parameters:
/// - mapView: 地图视图
/// - view: 大头针视图
/// - newState: 拖拽后的状态
/// - oldState: 拖拽前的状态
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, didChange newState: MKAnnotationViewDragState, fromOldState oldState: MKAnnotationViewDragState) {
print(oldState.rawValue, newState.rawValue)
}
}
效果图
4.2 自定义大头针和弹框
- 仍旧是那个代理方法
如果想要自定义大头针视图,必须使用MKAnnotationView或者自定义它的子类
代码实现
/// 在地图上添加大头针视图时就会来到此方法
///
/// - Parameters:
/// - mapView: 地图视图
/// - annotation: 大头针视图模式
/// - Returns: 创建好的大头针视图
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
// 创建标识
let pinID = "pinID"
// 通过标识从缓存池中取出大头针视图
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: pinID)
// 判断大头针视图是否存在,不存在就创建
if annotationView == nil {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: pinID)
}
// 更新大头针的数据模型
annotationView?.annotation = annotation
// 设置大头针的样式
annotationView?.image = UIImage(named: "category_4")
// 设置允许弹框
annotationView?.canShowCallout = true
// 设置弹框左边的控件
let leftImageV = UIImageView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
let leftImage = UIImage(named: "eason.jpg")
leftImageV.image = leftImage
annotationView?.leftCalloutAccessoryView = leftImageV
// 设置弹框右边的控件
let rightImageV = UIImageView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
let rightImage = UIImage(named: "huba.jpeg")
rightImageV.image = rightImage
annotationView?.rightCalloutAccessoryView = rightImageV
// 设置弹框底部的控件
annotationView?.detailCalloutAccessoryView = UISwitch()
return annotationView
}
效果图