iOS14開發-定位與地圖

定位

CoreLocation 是 iOS 中用於設備定位的框架。經過這個框架能夠實現定位進而獲取位置信息如經度、緯度、海拔信息等。git

模塊與常見類

  • 定位所包含的類都在CoreLocation模塊中,使用時必須導入。
  • CLLocationManager:定位管理器,能夠理解爲定位不能本身工做,須要有個類對它進行全過程管理。
  • CLLocationManagerDelegate:定位管理代理,不論是定位成功與失敗,都會有相應的代理方法進行回調。
  • CLLocation:表示某個位置的地理信息,包含經緯度、海拔等。
  • CLPlacemark:位置信息,包含的信息如國家、城市、街道等。
  • CLGeocoder:地理編碼。

工做流程

  1. 建立CLLocationManager,設置代理併發起定位。
  2. 實現CLLocationManagerDelegate中定位成功和失敗的代理方法。
  3. 在成功的代理方法中獲取CLLocation對象並經過CLGeocoder進行反向地理編碼獲取對應的位置信息CLPlacemark
  4. 經過CLPlacemark獲取具體的位置信息。

權限

受權對話框

  • 程序中調用requestWhenInUseAuthorization發起定位受權。
  • 程序中調用requestAlwaysAuthorization發起定位受權。

前臺定位

  • 須要在 Info.plist 中配置Privacy - Location When In Use Usage Description
  • 程序中調用requestWhenInUseAuthorization發起定位受權。
  • 彈出的受權對話框新增了精確位置開關,同時新增了小地圖展現當前位置。

後臺定位

  • 須要勾選 Capabilities —> Background Modes —> Location updates
  • 程序中容許後臺定位:locationManager.allowsBackgroundLocationUpdates = true
  • 此時受權分爲 2 種狀況:

(1)Privacy - Location When In Use Usage Description + requestWhenInUseAuthorization:能夠後臺定位,但會在設備頂部出現藍條(劉海屏設備會出如今左邊劉海)。 (2)Privacy - Location When In Use Usage Description + Privacy - Location Always and When In Use Usage Description + requestAlwaysAuthorization:能夠後臺定位,不會出現藍條。這種方式會出現 2 次受權對話框:第一次和前臺定位同樣,在贊成使用While Using App模式後,繼續使用定位纔會彈出第二次,詢問是否切換到Always模式。swift

精度控制

  • iOS 14 新增了一種定位精度控制,在定位受權對話框中有一個精度切換開關,能夠切換精確和模糊定位(默認精確)。
  • 能夠經過CLLocationManageraccuracyAuthorization屬性獲取當前的定位精度權限。
  • 當已經得到定位權限且當前用戶選擇的是模糊定位,則可使用CLLocationManagerrequestTemporaryFullAccuracyAuthorization(withPurposeKey purposeKey: String, completion: ((Error?) -> Void)? = nil)方法申請一次臨時精肯定位權限,其中purposeKey爲 Info.plist 中配置的Privacy - Location Temporary Usage Description Dictionary字段下某個具體緣由的 key,能夠設置多個 key 以應對不一樣的定位使用場景。
  • requestTemporaryFullAccuracyAuthorization方法並不能用於申請定位權限,只能用於從模糊定位升級爲精肯定位;若是沒有得到定位權限,直接調用此 API 無效。
  • 若是不想使用精肯定位,則能夠在 Info.plist 中配置Privacy - Location Default Accuracy ReducedYES,此時申請定位權限的小地圖中再也不有精度切換開關。須要注意 2 點:

(1)若是發現該字段不是 Bool 型,須要以源碼形式打開 Info.plist,而後手動修改<key>NSLocationDefaultAccuracyReduced</key>爲 Bool 型的值,不然沒法生效。 (2)配置該字段後,若是 Info.plist 中還配置了Privacy - Location Temporary Usage Description Dictionary,則仍能夠經過requestTemporaryFullAccuracyAuthorization申請臨時的精肯定位權限,會再次彈出受權對話框進行確認。markdown

模擬器定位

因爲定位須要 GPS,通常狀況下須要真機進行測試。但對於模擬器,也能夠進行虛擬定位,主要有 3 種方式。併發

  • 方式一

(1)新建一個gpx文件,能夠取名XXX.gpx,而後將本身的定位信息填寫進 xml 對應的位置。 (2)gpx文件設置完成之後,首先須要運行一次 App,而後選擇Edit Scheme,在Options中選擇本身的gpx文件,這樣模擬器運行的時候就會讀取該文件的位置信息。而後能夠選擇Debug—>Simulate Location或底部調試欄上的定位按鈕進行gpx文件或位置信息的切換。框架

<?xml version="1.0"?>
<gpx version="1.1" creator="Xcode">
    <!--安徽商貿職業技術學院 谷歌地球:31.2906511800,118.3623587000-->
    <wpt lat="31.2906511800" lon="118.3623587000">
        <name>安徽商貿職業技術學院</name>
        <cmt>中國安徽省蕪湖市弋江區文昌西路24號 郵政編碼: 241002</cmt>
        <desc>中國安徽省蕪湖市弋江區文昌西路24號 郵政編碼: 241002</desc>
    </wpt>
</gpx>
複製代碼
  • 方式二:運行程序開始定位 —> 模擬器菜單 —> Features —> Location —> Custom Location —> 輸入經緯度。

實現步驟

  1. 導入CoreLocation模塊。
  2. 建立CLLcationManager對象,設置參數和代理,配置 Info.plist 並請求定位受權。
  3. 調用CLLcationManager對象的startUpdatingLocation()requestLocation()方法進行定位。
  4. 實現代理方法,在定位成功的方法中進行位置信息的處理。
import CoreLocation
import UIKit

class ViewController: UIViewController {
    // CLLocationManager
    lazy var locationManager = CLLocationManager()
    // CLGeocoder
    lazy var gecoder = CLGeocoder()

    override func viewDidLoad() {
        super.viewDidLoad()

        setupManager()
    }

    func setupManager() {
        // 默認狀況下每當位置改變時LocationManager就調用一次代理。經過設置distanceFilter能夠實現當位置改變超出必定範圍時LocationManager才調用相應的代理方法。這樣能夠達到省電的目的。
        locationManager.distanceFilter = 300
        // 精度 好比爲10 就會盡可能達到10米之內的精度
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        // 代理
        locationManager.delegate = self
        // 第一種:能後臺定位可是會在頂部出現大藍條(打開後臺定位的開關)
        // 容許後臺定位
        locationManager.allowsBackgroundLocationUpdates = true
        locationManager.requestWhenInUseAuthorization()
        // 第二種:能後臺定位而且不會出現大藍條
        // locationManager.requestAlwaysAuthorization()
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        // 如下2個方法都會調用代理方法
        // 1. 發起位置更新(定位)會一直輪詢,耗電
        locationManager.startUpdatingLocation()
        // 2. 只請求一次用戶的位置,省電
        // locationManager.requestLocation()
    }
}

extension ViewController: CLLocationManagerDelegate {
    // 定位成功
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        if let location = locations.last {
            // 反地理編碼轉換成具體的地址
            gecoder.reverseGeocodeLocation(location) { placeMarks, _ in
                // CLPlacemark -- 國家 城市 街道
                if let placeMark = placeMarks?.first {
                    print(placeMark)
                    // print("\(placeMark.country!) -- \(placeMark.name!) -- \(placeMark.locality!)")
                }
            }
        }
        // 中止位置更新
        locationManager.stopUpdatingLocation()
    }

    // 定位失敗
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print(error.localizedDescription)
    }
}
複製代碼

地圖

  • 地圖所包含的類都在MapKit模塊中,使用時必須導入。
  • 除了能夠顯示地圖,還支持在地圖上進行標記處理。
  • 地圖看似很複雜,其實它僅僅是一個控件 MKMapView,就和之前學習過的 UIButton、UITableView 等同樣,能夠在 storyboard 和代碼中使用。
  • 地圖上若是想要顯示用戶的位置,必須與定位配合,那麼就須要建立定位管理器、設置權限等(參考定位知識),同時須要經過 storyboard 或者代碼設置地圖的相關屬性。

準備工做

  1. 添加一個地圖並設置相關屬性。
  2. Info.plist 中配置定位權限。
  3. 建立 CLLocationManager 對象並請求定位權限。

基本使用

顯示地圖,同時顯示用戶所處的位置。點擊用戶的位置,顯示一個氣泡展現用戶位置的具體信息。dom

import MapKit

class ViewController: UIViewController {
    @IBOutlet var mapView: MKMapView!
    lazy var locationManager: CLLocationManager = CLLocationManager()

    override func viewDidLoad() {
        super.viewDidLoad()

        setupMapView()
    }

    func setupManager() {
        locationManager.requestWhenInUseAuthorization()
        // 不須要發起定位
    }

    func setupMapView() {
        // 設置定位
        setupManager()
        // 地圖類型
        mapView.mapType = .hybridFlyover
        // 顯示興趣點
        mapView.showsPointsOfInterest = true
        // 顯示指南針
        mapView.showsCompass = true
        // 顯示交通
        mapView.showsTraffic = true
        // 顯示建築
        mapView.showsBuildings = true
        // 顯示級別
        mapView.showsScale = true
        // 用戶跟蹤模式
        mapView.userTrackingMode = .followWithHeading
    }
}
複製代碼

縮放級別

在以前功能的基礎上實現地圖的任意視角(「縮放級別」)。ide

// 設置「縮放級別」
func setRegion() {
    if let location = location {
        // 設置範圍,顯示地圖的哪一部分以及顯示的範圍大小
        let region = MKCoordinateRegion(center: mapView.userLocation.coordinate, latitudinalMeters: 500, longitudinalMeters: 500)
        // 調整範圍
        let adjustedRegion = mapView.regionThatFits(region)
        // 地圖顯示範圍
        mapView.setRegion(adjustedRegion, animated: true)
    }
}
複製代碼

標註

在地圖上能夠添加標註來顯示一個個關鍵的信息點,用於對用戶的提示。學習

分類

  • MKPinAnnotationView:系統自帶的標註,繼承於 MKAnnotationView,形狀跟棒棒糖相似,能夠設置糖的顏色,和顯示的時候是否有動畫效果 (Swift 不推薦使用)。
  • MKMarkerAnnotationView:iOS 11 推出,建議使用。
  • MKAnnotationView:能夠用指定的圖片做爲標註的樣式,但顯示的時候沒有動畫效果,若是沒有指定圖片會什麼都不顯示(自定義時使用)。

建立模型

class MapFlag: NSObject, MKAnnotation {
    // 標題
    let title: String?
    // 副標題
    let subtitle: String?
    // 經緯度
    let coordinate: CLLocationCoordinate2D
    // 附加信息
    let urlString: String

    init(title: String?, subtitle: String?, coordinate: CLLocationCoordinate2D, urlString: String) {
        self.title = title
        self.subtitle = subtitle
        self.coordinate = coordinate
        self.urlString = urlString
    }
}
複製代碼

添加標註

  • 添加系統標註,點擊可以顯示標題和副標題。
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    let flag = MapFlag(title: "標題", subtitle: "副標題", coordinate: CLLocationCoordinate2D(latitude: 31.2906511800, longitude: 118.3623587000), urlString: "https://www.baidu.com")
    mapView.addAnnotation(flag)
}
複製代碼
  • 添加系統標註,點擊以氣泡形式顯示標題、副標題及自定義內容,此時須要重寫地圖的代理方法,返回標註的樣式。
extension ViewController: MKMapViewDelegate {
    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
        guard let annotation = annotation as? MapFlag else {
            return nil
        }
        // 若是是用戶的位置,使用默認樣式
        if annotation == mapView.userLocation {
            return nil
        }
        // 標註的標識符
        let identifier = "marker"
        // 獲取AnnotationView
        var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MKMarkerAnnotationView
        // 判空
        if annotationView == nil {
            annotationView = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: identifier)
            // 顯示氣泡
            annotationView?.canShowCallout = true
            // 左邊顯示的輔助視圖
            annotationView?.leftCalloutAccessoryView = UIImageView(image: UIImage(systemName: "heart"))
            // 右邊顯示的輔助視圖
            let button = UIButton(type: .detailDisclosure, primaryAction: UIAction(handler: { _ in
                print(annotation.urlString)
            }))
            annotationView?.rightCalloutAccessoryView = button
        }

        return annotationView
    }
}
複製代碼
  • 若是但願標註的圖標爲自定義樣式,只須要稍加更改代理方法並設置本身的標註圖片便可。
extension ViewController: MKMapViewDelegate {
    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
        guard let annotation = annotation as? MapFlag else {
            return nil
        }
        // 若是是用戶的位置,使用默認樣式
        if annotation == mapView.userLocation {
            return nil
        }
        // 標註的標識符
        let identifier = "custom"
        // 標註的自定義圖片
        let annotationImage = ["pin.circle.fill", "car.circle.fill", "airplane.circle.fill", "cross.circle.fill"]
        // 獲取AnnotationView
        var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier)
        // 判空
        if annotationView == nil {
            annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: identifier)
            // 圖標,每次隨機取一個
            annotationView?.image = UIImage(systemName: annotationImage.randomElement()!)
            // 顯示氣泡
            annotationView?.canShowCallout = true
            // 左邊顯示的輔助視圖
            annotationView?.leftCalloutAccessoryView = UIImageView(image: UIImage(systemName: "heart"))
            // 右邊顯示的輔助視圖
            let button = UIButton(type: .detailDisclosure, primaryAction: UIAction(handler: { _ in
                print(annotation.urlString)
            }))
            annotationView?.rightCalloutAccessoryView = button
            // 彈出的位置偏移
            annotationView?.calloutOffset = CGPoint(x: -5.0, y: 5.0)
        }

        return annotationView
    }
}

// 點擊地圖插入一個標註,標註的標題和副標題顯示的是標註的具體位置
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    let touchPoint = touches.first?.location(in: mapView)
    // 將座標轉換成爲經緯度,而後賦值給標註
    let coordinate = mapView.convert(touchPoint!, toCoordinateFrom: mapView)
    let location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
    let gecoder = CLGeocoder()
    // 反地理編碼轉換成具體的地址
    gecoder.reverseGeocodeLocation(location) { placeMarks, _ in
        let placeMark = placeMarks?.first
        if let placeMark = placeMark {
            let flag = MapFlag(title: placeMark.locality, subtitle: placeMark.subLocality, coordinate: coordinate, urlString: "https://www.baidu.com")
            self.mapView.addAnnotation(flag)
        }
    }
}
複製代碼
相關文章
相關標籤/搜索