地圖 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
}
效果圖

