iOS 地图定位及大头针的基本使用

地图 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

三.大头针的基本使用 - 添加/删除

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)
    
    }

效果图

添加大头针1

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
    }

效果图

自定义大头针和弹框

posted @ 2016-11-13 18:56  Oo11  阅读(5510)  评论(0编辑  收藏  举报