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
    }

效果圖

自定義大頭針和彈框


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM