作者:柳駿
原文地址:http://www.ios520junge.com/
初學(xué)地圖時,覺得地圖是個很高深的玩意兒瞧柔,導(dǎo)航薪夕、定位、檢索這得運(yùn)用多少算法和核心動畫的知識點(diǎn)啊酷誓,于是一直排斥披坏。
后來開始著手研究,也不敢直接去研究它底部怎么用盐数,而是直接用的百度的SDK棒拂,然而用了更是覺得惡心,因?yàn)橐砑雍芏囔o態(tài)庫和包玫氢,導(dǎo)致一個小demo就幾百M(fèi)帚屉,想想有幾個用戶愿意下載一個地圖就幾百M(fèi)啊,于是咨詢過幾個專門做這塊的朋友琐旁,并查閱了很多這方面的資料涮阔,發(fā)現(xiàn)這洞真的很深。
** 但是想要運(yùn)用做些APP里面導(dǎo)航灰殴、定位敬特、追蹤掰邢、檢索、自定義大頭針確是很簡單的伟阔,基本掌握2個蘋果原生的類 CoreLocation 和 MapKit 你就能把地圖玩的很6了(如果想深究底層的看到這里請return)**
下面簡單介紹下這2個類的使用辣之,看完基本上APP上要用到地圖的一些基本功能你都會做了,由于最近一直用的swift3.0皱炉,下面的代碼都是用的swift3.0寫的
CoreLocation
一怀估、版本適配、授權(quán)
- iOS8.0之前合搅、在iOS6.0之后,蘋果非常重視用戶的隱私,只要APP訪問用戶的隱私都會進(jìn)行彈框讓用戶授權(quán)(通訊錄,相機(jī),位置..健康)
- 如果想要提高用戶點(diǎn)擊ok的幾率,需要在info.plist文件中配置一個key:Privacy – Location Usage Description
- 建議:key的值不要亂寫,找PM(產(chǎn)品經(jīng)理),PD(開發(fā)人員)要文案
默認(rèn)情況下,只能在前臺獲取用戶的位置信息,如果在后臺也想獲取用戶的位置信息,需要開啟后臺模式 - iOS8.0之后
想要獲取用戶的位置信息,需要主動請求授權(quán)(前臺定位授權(quán)or前后臺定位授權(quán))
** 申請前臺定位授權(quán):**
- 必須在info.plist文件中配置對應(yīng)的key NSLocationWhenInUseUsageDescription
- 默認(rèn)情況下只能在前臺獲取用戶的位置信息(用戶進(jìn)入軟件才能獲取位置信息)
- 如果想要在后臺也能獲取用戶的位置信息,需要:開啟后臺模式 location updates
- 在后臺獲取用戶的位置信息,系統(tǒng)會在頂部顯示藍(lán)色橫幅,時時提示用戶該app在獲取你的位置信息,點(diǎn)擊藍(lán)色橫幅則可以打開該app(這個是不好的,小心用戶卸了你的APP)
locationM.requestWhenInUseAuthorization()// 前臺定位授權(quán)
** 申請前后臺定位授權(quán): **
- 不會出現(xiàn)藍(lán)色橫幅
- 必須在info.plist文件中配置對應(yīng)的key NSLocationAlwaysUsageDescription
- 在前臺和后臺都能夠獲取用戶的位置信息,在后臺獲取用戶的位置信息,不需要開啟后臺模式
locationM.requestAlwaysAuthorization()// 前后臺定位授權(quán)
可不用再background Modes里面勾選location updates
- iOS9.0之后
申請前臺定位授權(quán):
1.需要開啟后臺模式: location updates
2.必須允許后臺獲取用戶的位置信息(下面的版本適配代碼)
3.在后臺獲取用戶的位置信息,也會出現(xiàn)iOS8.0的那個藍(lán)色橫幅提示信息
注意點(diǎn): 如果允許后臺獲取用戶的位置信息,必須勾選后臺模式,否則此代碼會造成崩潰
if #available(iOS 9.0, *)
{
locationM.allowsBackgroundLocationUpdates = true
}
** 申請前后臺定位授權(quán):相對于iOS8.0,沒有什么更新的東西**
二.監(jiān)聽狀態(tài)
定位是使用CoreLocation這個框架實(shí)現(xiàn)的,而跟定位離不開關(guān)系的是位置管理者 CLLocationManager,使用位置管理者的時候,通常會提供一個懶加載的方法來保證他不被銷毀.蘋果給他提供了代理的接口,外界可以通過設(shè)置代理來獲取他所能管理的所有東西和動態(tài).下面介紹一些代理方法的功能:
/// 當(dāng)獲取到用戶的位置的時候會來到該方法
/// – Parameters:
/// – manager: 位置管理者
/// – locations: 位置數(shù)組
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print(“定位到了”)
}
/// 當(dāng)授權(quán)狀態(tài)發(fā)生改變的時候會來到該方法(授權(quán)狀態(tài)不受開發(fā)人員控制,由用戶控制)
/// – Parameters:
/// – manager: 位置管理者
/// – status: 授權(quán)狀態(tài)( notDetermined, denied, restricted, authorizedAlways, authorizedWhenInUse)
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {}
注意:當(dāng)用戶自己拒絕APP對他進(jìn)行位置定位的時或手機(jī)主人之前已經(jīng)把手機(jī)的定位功能關(guān)閉時,status都是拒絕狀態(tài),如果手機(jī)的定位功能處于關(guān)閉狀態(tài),蘋果會自動跳轉(zhuǎn)到手機(jī)設(shè)置界面,但是如果是用戶自己在打開APP時直接選擇拒絕的,蘋果不會自動跳轉(zhuǎn)手機(jī)設(shè)置界面,需要開發(fā)者做友情提示用戶自己去開啟.
** 手機(jī)上這2個選項(xiàng)由開發(fā)者在info.plist文件是否添加了這2個key:Privacy – Location Always Usage Description多搀、Privacy – Location When In Use Usage Description,value決定彈框提醒是否允許授權(quán)上的提示語句 **
如果用戶關(guān)閉了定位服務(wù)灾部,就是真正拒絕康铭,那么提示并彈框讓他去設(shè)置
if CLLocationManager.locationServicesEnabled() { // 位置服務(wù)開啟
print(“用戶真正拒絕”)
if #available(iOS 8.0, *) {
//設(shè)置要打開的URL為“設(shè)置”
let url = URL(string: UIApplicationOpenSettingsURLString)!
if UIApplication.shared.canOpenURL(url) {//如果允許打開
//點(diǎn)擊“設(shè)置”,打開“設(shè)置”赌髓,去允許定位
UIApplication.shared.openURL(url)
}
** 如果第一次沒有點(diǎn)setting从藤,點(diǎn)了cancel,后面不會有提醒去設(shè)置 **
三锁蠕、參數(shù)設(shè)置
1夷野、設(shè)置多少米 獲取用戶的位置一次
// 1: 獲取1次 2: 獲取111KM/100M次
// 下一個位置相對于上一個位置大于100米,就會定位一次
locationM.distanceFilter = 1002、設(shè)置定位精確度
//AccuracyBestForNavigation : 最適合導(dǎo)航
//AccuracyBest : 最好的
//AccuracyNearestTenMeters : 附近10米
//AccuracyHundredMeters : 附近100米
//AccuracyKilometer : 附近1000米
//AccuracyThreeKilometers : 附近3000米
** 注:定位的精確度越高,越消耗電荣倾,盡量給用戶省電,在能夠滿足需求情況下,盡量使用低精確度 **
locationM.desiredAccuracy = kCLLocationAccuracyBest
四悯搔、開始定位方法選擇
1、locationM.startUpdatingLocation()
** 調(diào)用這個方法,就會不斷地獲取用戶的位置信息 **
優(yōu)點(diǎn): 定位精確度高
缺點(diǎn): 比較耗電,app必須運(yùn)行著才能獲取用戶的位置信息
** 標(biāo)準(zhǔn)定位服務(wù)(基于GPS/wifi/藍(lán)牙/蜂窩基站) **
具體使用什么方式進(jìn)行定位由蘋果決定
1>GPS:定位最準(zhǔn)確,缺點(diǎn):如果被建筑物擋住,就檢測不到了
2>wifi:GPS不能使用則使用wifi
3>蜂窩基站: 不能使用wifi,則使用蜂窩基站
4>藍(lán)牙 : iwatch藍(lán)牙連接手機(jī)
2逃呼、locationM.startMonitoringSignificantLocationChanges()
** 監(jiān)聽重大位置改變(基于基站定位) **
優(yōu)點(diǎn): 比較省電,就算app被完全殺死,也可以獲取用戶的位置信息,當(dāng)用戶的位置發(fā)生改變的時候,會自動啟動該app在后臺執(zhí)行,來獲取用戶的位置信息
缺點(diǎn):
1>定位精確度低,硬性要求必須有電話模塊
2>請求一次精確度最高的用戶位置信息
3>根據(jù)精確度來獲取用戶的位置信息
4>注意點(diǎn):必須實(shí)現(xiàn)定位失敗的代理方法,
locationManager(_ manager: CLLocationManager, didFailWithError error: Error)
不能與startUpdatingLocation
一起使用
五鳖孤、CLLocation對象
位置對象的一些屬性:
coordinate (CLLocationCoordinate2D): 經(jīng)緯度信息,是個結(jié)構(gòu)體抡笼,里面有經(jīng)度和緯度苏揣。這個結(jié)構(gòu)體可將鼠標(biāo)在地圖上點(diǎn)擊的坐標(biāo)點(diǎn)轉(zhuǎn)成經(jīng)緯度
將控件上面的點(diǎn)(CGPoint),轉(zhuǎn)為經(jīng)緯度
let coordinate = mapView.convert(point!, toCoordinateFrom: mapView)```
>altitude : 海拔
horizontalAccuracy : 水平精確度,如果為負(fù)數(shù),代表位置不可用
verticalAccuracy : 垂直精確度,如果為負(fù)數(shù),代表海拔不可用
course : 航向,如果為負(fù)數(shù),代表航向不可用
speed : 速度,如果為負(fù)數(shù),代表速度不可用
timestamp : 獲取當(dāng)前定位到的時間
distance(from location: CLLocation) : 計算2個位置之間的實(shí)際物理距離
如下在代理方法中,拿到位置對象所有屬性:
```obj
func locationManager(_ manager: CLLocationManager,
didUpdateLocations locations: [CLLocation])
{
guard let location = locations.last
else {
return
}
print(location)
}```
打印結(jié)果:
<+37.17858340,-122.40641700> +/- 5.00m (speed -1.00 mps / course -1.00) @ 11/11/16, 10:05:05 PM China Standard Time
<+37.17858340(緯度),-122.40641700(經(jīng)度)> +/- 5.00m(水平精確度) (speed (速度)-1.00 mps / course(航向) -1.00) @ 11/11/16, 10:05:05 PM China Standard Time(獲取當(dāng)前位置的時間)
####應(yīng)用:指南針
```obj
// 開始獲取設(shè)備朝向
if CLLocationManager.headingAvailable() {
locationM.startUpdatingHeading()
} else {
print(“磁力計損壞,建議更換手機(jī)”)
}```
當(dāng)獲取到一個新的設(shè)備朝向時就會來到這個方法
```obj
//manager: 位置管理者
/// – newHeading: 新的設(shè)備朝向推姻,為負(fù)數(shù)的話平匈,朝向不可用
func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading)
{ }
獲取磁北角度
let magneticHeading = newHeading.magneticHeading
六、區(qū)域監(jiān)聽(CLCircularRegion)
1>主動請求用戶的位置對于這個區(qū)域的狀態(tài)
2>一個區(qū)域被監(jiān)聽之后,才能去請求這個區(qū)域的狀態(tài)
3>當(dāng)一個區(qū)域能被監(jiān)聽之后(didStartMonitoringFor),會把該區(qū)域放入一個集合中monitoredRegions
4>這個集合最多能存儲20個區(qū)域
七藏古、地理編碼(CLGeocoder)
必須聯(lián)網(wǎng)
地理編碼:
.geocodeAddressString(“廣州”, completionHandler: {(clpls : [CLPlacemark]?,error : Error? )in
print(“地理編碼成功”)
})```
反地理編碼:
```obj
geoc.reverseGeocodeLocation(location, completionHandler: {(clpls : [CLPlacemark]?,error : Error? )in
print(“反地理編碼成功”)
})```
地標(biāo)對象(CLPlacemark):
location : 用戶的位置.(經(jīng)緯度信息)
name : 地址
locality : 城市
country : 國家
##MapKit
###一增炭、地圖基本使用
** 使用前要導(dǎo)入頭文件import MapKit**
* 1.設(shè)置地圖的類型(mapType)
>case standard :普通地圖(默認(rèn))
case satellite : 衛(wèi)星云圖
case hybrid : 混合地圖(衛(wèi)星云圖的基礎(chǔ)上加上普通地圖)
@available(iOS 9.0, *)
case satelliteFlyover :3D衛(wèi)星地圖 // 做適配
case hybridFlyover :3D混合衛(wèi)星地圖(3D衛(wèi)星地圖 + 普通地圖) // 做適配
>** 如設(shè)置為默認(rèn):mapView.mapType = .standard **
* 2.設(shè)置地圖的操作項(xiàng)
>禁止縮放:mapView.isZoomEnabled = false
禁止旋轉(zhuǎn):mapView.isRotateEnabled = false
禁止拖動:mapView.isScrollEnabled = false
* 3.設(shè)置地圖的顯示項(xiàng)
>iOS 9.0以后有的:
比例尺:mapView.showsScale = true
指南針:mapView.showsCompass = true
顯示交通:mapView.showsTraffic = true
>iOS 9.0之前有的:
顯示建筑物:mapView.showsBuildings = true
顯示興趣點(diǎn): mapView.showsPointsOfInterest = true
```obj
// 顯示用戶的位置
// 在iOS8.0之后需要主動請求授權(quán),調(diào)用locationM的getter方法
_ = locationM
mapView.showsUserLocation = true```
設(shè)置用戶的追蹤模式
>case none // 不追蹤,也不會顯示用戶的位置(相當(dāng)于showsUserLocation為false)
case follow // 追蹤,會顯示用戶的位置showsUserLocation為true
case followWithHeading // 帶放心的追蹤 showsUserLocation為true
// 有一個缺陷,只要動一下地圖,就不再追蹤用戶的位置(不靈光)
>// mapView.userTrackingMode = .followWithHeading
4.定位拧晕、追蹤
>步驟:
1隙姿、添加MapView到控制器view上面,并導(dǎo)入頭文件import MapKit厂捞,可顯示標(biāo)準(zhǔn)的默認(rèn)地圖
2输玷、設(shè)置地圖的類型(mapType:standard队丝、satellite、hybrid欲鹏、satelliteFlyover机久、hybridFlyover)
3、設(shè)置地圖的操作項(xiàng)(縮放赔嚎、旋轉(zhuǎn)膘盖、拖動)
4、設(shè)置地圖的顯示項(xiàng)(比例尺尤误、指南針侠畔、交通、建筑物损晤、興趣點(diǎn))
5践图、顯示用戶的位置(iOS8.0后需要主動請求定位授權(quán))
6、設(shè)置用戶的追蹤模式(none沉馆、follow、followWithHeading)
7德崭、設(shè)置地圖的代理斥黑,當(dāng)用戶位置改變,會調(diào)用代理方法
8眉厨、在代理方法:①設(shè)置大頭針標(biāo)題锌奴、子標(biāo)題;②把用戶的位置設(shè)置為地圖的中心點(diǎn)
代碼實(shí)現(xiàn):
```obj
import UIKit
import MapKit
class ViewController: UIViewController {
@IBOutlet weak var mapView: MKMapView!
lazy var locationM:CLLocationManager = {
let locationM = CLLocationManager()
//請求前后臺授權(quán)定位
locationM.requestAlwaysAuthorization()
return locationM
}()
override func viewDidLoad() {
super.viewDidLoad()
// 1憾股、添加MapView到控制器view上面鹿蜀,并導(dǎo)入頭文件import MapKit,可顯示標(biāo)準(zhǔn)的默認(rèn)地圖
// 2服球、設(shè)置地圖的類型(standard茴恰、satellite、hybrid斩熊、satelliteFlyover往枣、hybridFlyover)
mapView.mapType = .standard
// 3、設(shè)置地圖的操作項(xiàng)(縮放粉渠、旋轉(zhuǎn)分冈、拖動)–> 默認(rèn)都開了,所以不設(shè)置
// 4霸株、設(shè)置地圖的顯示項(xiàng)(比例尺雕沉、指南針、交通去件、建筑物坡椒、興趣點(diǎn))
mapView.showsBuildings = true //建筑物
mapView.showsPointsOfInterest = true//興趣點(diǎn)
if #available(iOS 9.0, *) {
mapView.showsScale = true //比例尺
mapView.showsCompass = true //指南針
mapView.showsTraffic = true //交通
}
// 5扰路、顯示用戶的位置(iOS8.0后需要主動請求定位授權(quán))–> 懶加載位置管理者
_ = locationM //主動請求授權(quán),配置info.plist文件
mapView.showsUserLocation = true
// 6肠牲、設(shè)置用戶的追蹤模式(none幼衰、follow、followWithHeading)
mapView.userTrackingMode = .follow//設(shè)置了這個可以不用設(shè)置showsUserLocation缀雳,區(qū)域跨度比設(shè)置showsUserLocation更小渡嚣,顯示的用戶位置更詳細(xì)
// 7、設(shè)置地圖的代理肥印,當(dāng)用戶位置改變识椰,會調(diào)用代理方法
mapView.delegate = self
// 8、在代理方法:①設(shè)置大頭針標(biāo)題深碱、子標(biāo)題腹鹉;②把用戶的位置設(shè)置為地圖的中心點(diǎn)
}
}
//MARK:- mapView的代理方法
extension ViewController:MKMapViewDelegate{
//當(dāng)用戶位置改變,會調(diào)用代理方法
func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation) {
//①設(shè)置大頭針標(biāo)題敷硅、子標(biāo)題功咒;
userLocation.title = “我的位置”
userLocation.subtitle = “廣州市、天河區(qū)绞蹦、盛達(dá)商務(wù)園……”
//②把用戶的位置設(shè)置為地圖的中心點(diǎn)
let coordinate = userLocation.coordinate //通過用戶的位置力奋,獲取經(jīng)緯度
//方法一:
// 可以實(shí)現(xiàn)追蹤用戶的位置,缺陷:默認(rèn)情況下不會放大地圖的顯示區(qū)域,需要手動放大幽七,會把之前設(shè)置的userTrackingMode為follow變回none
// mapView.setCenter(coordinate, animated: true)
//方法二:
//span區(qū)域跨度:在地圖上 東西經(jīng)各180度,總共360度,顯示的區(qū)域跨度為0-360度之間,南北緯各90度,總共180度,顯示的區(qū)域跨度為0-180度景殷,設(shè)置的越小,那么看到的內(nèi)容就越清晰
let span = MKCoordinateSpan(latitudeDelta: 0.00001, longitudeDelta: 0.00001)
//center : 地圖的中心點(diǎn)(經(jīng)度和緯度)
let region = MKCoordinateRegion(center: coordinate, span: span)
//設(shè)置地圖的顯示區(qū)域
mapView.setRegion(region, animated: true)
}
//當(dāng)?shù)貓D顯示區(qū)域改變時,會調(diào)用這個方法
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
print(mapView.region)
}
}
** 5.大頭針對象(MKAnnotation)**
按照MVC的原則
注:在地圖上操作大頭針,實(shí)際上是控制大頭針數(shù)據(jù)模型
- 添加大頭針就是添加大頭針數(shù)據(jù)模型
- 刪除大頭針就是刪除大頭針數(shù)據(jù)模型
注:不能直接用系統(tǒng)的MKAnnotation澡屡,要自定義一個大頭針模型猿挚,因?yàn)橄到y(tǒng)的能操控大頭針屬性的全部是只讀的
步驟:
1、獲取在控件上點(diǎn)擊的點(diǎn)
2驶鹉、將控件上面的點(diǎn)(CGPoint),轉(zhuǎn)為經(jīng)緯度
3绩蜻、創(chuàng)建大頭針數(shù)據(jù)模型,并添加到地圖上:注:必須先設(shè)置title和subTitle的占位字
①通過模型創(chuàng)建大頭針;
②確定大頭針的經(jīng)緯度(在地圖上顯示的位置)梁厉;
③設(shè)置大頭針彈框的標(biāo)題和子標(biāo)題辜羊;
④添加到地圖上
4、將點(diǎn)擊的那個點(diǎn)的經(jīng)緯度進(jìn)行 反地理編碼词顾,得到彈框要顯示的標(biāo)題和子標(biāo)題
① 獲取需要反地理編碼的經(jīng)緯度八秃,懶加載地理編碼對象:CLGeocoder
②判斷反地理編碼是否成功
③取出地標(biāo)對象(CLPlacemark)
④通過地標(biāo)對象獲取城市、詳細(xì)地址肉盹,更新大頭針標(biāo)題和子標(biāo)題
代碼實(shí)現(xiàn):
import UIKit
import MapKit
class ViewController: UIViewController {
@IBOutlet weak var mapView: MKMapView!
/// 地理編碼
lazy var geoc:CLGeocoder = {
return CLGeocoder()
}()
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
// 1昔驱、獲取在控件上點(diǎn)擊的點(diǎn)
let point = touches.first?.location(in: mapView)
// 2、將控件上面的點(diǎn)(CGPoint),轉(zhuǎn)為經(jīng)緯度
let coordinate = mapView.convert(point!, toCoordinateFrom: mapView)
// 3上忍、創(chuàng)建大頭針數(shù)據(jù)模型,并添加到地圖上:注:必須先設(shè)置title和subTitle的占位字
let annotation = addAnnotation(coordinate: coordinate, title: “城市”, subTitle: “地址”)
// 4骤肛、將點(diǎn)擊的那個點(diǎn)的經(jīng)緯度進(jìn)行 反地理編碼纳本,得到彈框要顯示的標(biāo)題和子標(biāo)題
//①獲取需要反地理編碼的經(jīng)緯度,懶加載地理編碼對象:CLGeocoder
let location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
geoc.reverseGeocodeLocation(location, completionHandler: {
(clpls:[CLPlacemark]?, error:Error?) -> Swift.Void in
//②判斷反地理編碼是否成功
if error != nil{return}
//③取出地標(biāo)對象(CLPlacemark)
let clpl = clpls?.first
//④通過地標(biāo)對象獲取城市腋颠、詳細(xì)地址繁成,更新大頭針標(biāo)題和子標(biāo)題
annotation.title = clpl?.administrativeArea
annotation.subtitle = (clpl?.locality)! + (clpl?.subLocality)!
annotation.subtitle = annotation.subtitle! + (clpl?.name)!
})
}
/// 移除大頭針
@IBAction func removeAnnotation(_ sender: UIBarButtonItem) {
// 1.獲取需要移除的大頭針
let annotation = mapView.annotations
// 2.移除大頭針
mapView.removeAnnotations(annotation)
}
}
//MARK:- 創(chuàng)建大頭針
extension ViewController{
/// 創(chuàng)建大頭針,添加到地圖上
/// – Parameters:
/// – coordinate: 經(jīng)緯度
/// – title: 標(biāo)題
/// – subTitle: 小標(biāo)題
/// – Returns: 大頭針
func addAnnotation(coordinate:CLLocationCoordinate2D,title:String ,subTitle:String) -> JGAnnotation{
//①通過模型創(chuàng)建大頭針淑玫;
let annotation = JGAnnotation()
//②確定大頭針的經(jīng)緯度(在地圖上顯示的位置)巾腕;
annotation.coordinate = coordinate
//③設(shè)置大頭針彈框的標(biāo)題和子標(biāo)題;
annotation.title = title
annotation.subtitle = subTitle
//④添加到地圖上
mapView.addAnnotation(annotation)
return annotation
}
}
** 自定義大頭針:**
mapview提供了一個代理方法絮蒿,當(dāng)添加一個大頭針模型時尊搬,系統(tǒng)自動會調(diào)用這個代理方法,去設(shè)置大頭針的視圖土涝,具體步驟:
步驟跟創(chuàng)建tableViewCell一樣
1佛寿、創(chuàng)建標(biāo)識
2、從緩存池中獲取大頭針視圖
3但壮、如果沒有獲取到冀泻,就創(chuàng)建大頭針視圖
4、設(shè)置大頭針數(shù)據(jù)模型
5蜡饵、設(shè)置大頭針樣式(彈框腔长、彈框偏移、彈框的左右底部控件验残、大頭針偏移、下降動畫巾乳、拖拽)
//MARK:- mapview代理方法您没,自定義大頭針
extension ViewController:MKMapViewDelegate{
/// 當(dāng)添加一個大頭針模型時系統(tǒng)自動會調(diào)用這個方法,去設(shè)置大頭針視圖
/// 如果不實(shí)現(xiàn)這個方法胆绊,則默認(rèn)用系統(tǒng)的
/// – Parameters:
/// – mapView: 地圖視圖
/// – annotation: 大頭針數(shù)據(jù)模型
/// – Returns: 返回創(chuàng)建好的大頭針視圖
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
//步驟跟創(chuàng)建tableViewCell一樣
// 1氨鹏、創(chuàng)建標(biāo)識
let ID = “annotationID”
// 2、從緩存池中獲取大頭針視圖
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: ID)
// 3压状、如果沒有獲取到仆抵,就創(chuàng)建大頭針視圖
if annotationView == nil {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: ID)
}
// 4、設(shè)置大頭針數(shù)據(jù)模型
annotationView?.annotation = annotation
// 5种冬、設(shè)置大頭針樣式(彈框镣丑、彈框偏移、彈框的左右底部控件娱两、大頭針偏移莺匠、下降動畫、拖拽)
//如果自定義了大頭針視圖,必須要指定長什么樣
annotationView?.image = UIImage(named: “category_1”)
//必須設(shè)置允許彈框十兢,才會彈框
annotationView?.canShowCallout = true
//設(shè)置大頭針中心偏移量趣竣,彈框偏移量
// annotationView?.centerOffset = CGPoint(x: -50, y: 50)
// annotationView?.calloutOffset = CGPoint(x: 50, y: 50)
//設(shè)置大頭針可被拖拽摇庙,無法設(shè)置下降(animatesDrop)動畫了
annotationView?.isDraggable = true
//設(shè)置彈框左右視圖,底部視圖
let imageView1 = UIImageView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
imageView1.image = UIImage(named: “huba”)
annotationView?.leftCalloutAccessoryView = imageView1 //左邊視圖
let imageView2 = UIImageView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
imageView2.image = UIImage(named: “htl”)
annotationView?.rightCalloutAccessoryView = imageView2 //左邊視圖
//底部視圖遥缕,是iOS9.0之后有的
// if #available(iOS 9.0, *) {
// annotationView?.detailCalloutAccessoryView = UISwitch()
// }
return annotationView
}
}
二卫袒、導(dǎo)航
1、方案一:用系統(tǒng)自帶的app導(dǎo)航
步驟:
用系統(tǒng)自帶app導(dǎo)航单匣,反推法
1夕凝、打開系統(tǒng)導(dǎo)航app進(jìn)行導(dǎo)航:MKMapItem.openMaps
2、需要2個參數(shù):MKMapItem數(shù)組和操作項(xiàng)的字典
3封孙、操作項(xiàng)的字典:駕駛導(dǎo)航迹冤、地圖類型、是否顯示交通虎忌,敲MKlaunch會提醒出所有key
4泡徙、MKMapItem:起始點(diǎn)、結(jié)束點(diǎn)
5膜蠢、起點(diǎn)堪藐、終點(diǎn)需要傳地標(biāo)對象(通過地理編碼獲得)
import UIKit
import MapKit
class ViewController: UIViewController {
lazy var geoc:CLGeocoder = {
return CLGeocoder()
}()
override func viewDidLoad() {
super.viewDidLoad()
//用系統(tǒng)自帶app導(dǎo)航,反推法
// 1挑围、打開系統(tǒng)導(dǎo)航app進(jìn)行導(dǎo)航:MKMapItem.openMaps
// 2礁竞、需要2個參數(shù):MKMapItem數(shù)組和操作項(xiàng)的字典
// 3、操作項(xiàng)的字典:方向模式杉辙、地圖類型模捂、追蹤
// 4、MKMapItem:起始點(diǎn)蜘矢、結(jié)束點(diǎn)
// 5狂男、起點(diǎn)、終點(diǎn)需要傳地標(biāo)對象(通過地理編碼獲得)
//地理編碼起點(diǎn)品腹、終點(diǎn)
geoc.geocodeAddressString(“廣州”, completionHandler: {
(clpls:[CLPlacemark]?, error:Error?) -> Swift.Void in
if error != nil{return}
guard let startCLPL = clpls?.first else{return}
//獲取終點(diǎn)的地標(biāo)
self.geoc.geocodeAddressString(“贛州”, completionHandler: { (clpls:[CLPlacemark]?, error:Error?) in
if error != nil{return}
guard let endCLPL = clpls?.first else{return}
//打開導(dǎo)航
self.openToNavigationMap(startCLPL: startCLPL, endCLPL: endCLPL)
})
})
}
func openToNavigationMap(startCLPL:CLPlacemark,endCLPL:CLPlacemark) -> () {
//設(shè)置起點(diǎn)
let startPlace = MKPlacemark(placemark: startCLPL)
let startItem:MKMapItem = MKMapItem(placemark: startPlace)
//設(shè)置終點(diǎn)
let endPlace = MKPlacemark(placemark: endCLPL)
let endItem:MKMapItem = MKMapItem(placemark: endPlace)
//設(shè)置
let items:[MKMapItem] = [startItem,endItem]
//設(shè)置地圖操作項(xiàng)
let launchOptions : [String : Any] = [
MKLaunchOptionsDirectionsModeKey:MKLaunchOptionsDirectionsModeDriving,//駕駛
MKLaunchOptionsMapTypeKey:MKMapType.hybrid.rawValue,//混合地圖
MKLaunchOptionsShowsTrafficKey:true]//交通可見
//打開系統(tǒng)的導(dǎo)航APP進(jìn)行導(dǎo)航
MKMapItem.openMaps(with: items, launchOptions: launchOptions)
}
}
2岖食、方案二:發(fā)送網(wǎng)絡(luò)請求給蘋果服務(wù)器
發(fā)送網(wǎng)絡(luò)請求給蘋果服務(wù)器,獲取導(dǎo)航路線:反推法
1舞吭、創(chuàng)建路線請求對象
2泡垃、設(shè)置路線請求的原始點(diǎn)
3、設(shè)置路線請求的目的地點(diǎn)
4羡鸥、創(chuàng)建路線規(guī)劃對象(MKDirections)蔑穴,去請求路線信息
5、計算路線信息
6惧浴、將計算出來的路線的折線覆蓋層模型數(shù)據(jù) 添加到地圖澎剥,觸發(fā)代理方法,去設(shè)置覆蓋層的渲染圖層
①根據(jù)響應(yīng)獲得路線
②根據(jù)路線獲得折線數(shù)據(jù)模型
③將折線數(shù)據(jù)模型添加到地圖(路線也是覆蓋層,只要添加了覆蓋層就會觸發(fā)代理方法去渲染圖層)
7哑姚、在代理方法中畫出線路
import UIKit
import MapKit
class ViewController: UIViewController {
@IBOutlet weak var mapView: MKMapView!
/// 懶加載創(chuàng)建地理編碼對象
lazy var geoc:CLGeocoder = {
return CLGeocoder()
}()
lazy var locationM:CLLocationManager = {
let locationM = CLLocationManager()
locationM.requestAlwaysAuthorization()
return locationM
}()
override func viewDidLoad() {
super.viewDidLoad()
_ = locationM
mapView.showsUserLocation = true
mapView.userTrackingMode = .follow
mapView.delegate = self
//發(fā)送網(wǎng)絡(luò)請求給蘋果服務(wù)器祭饭,獲取導(dǎo)航路線:反推法
// 1、創(chuàng)建路線請求對象(MKDirectionsRequest)
// 2叙量、設(shè)置路線請求的原始點(diǎn)(地理編碼)
// 3倡蝙、設(shè)置路線請求的目的地點(diǎn)(地理編碼)
// 4、創(chuàng)建路線規(guī)劃對象(MKDirections)绞佩,去請求路線信息
// 5寺鸥、計算路線信息
// 6、將計算出來的路線的折線覆蓋層模型數(shù)據(jù) 添加到地圖品山,觸發(fā)代理方法胆建,去設(shè)置覆蓋層的渲染圖層
// 7、在代理方法中畫出線路
//原始點(diǎn)肘交、目的地點(diǎn)地理編碼
geoc.geocodeAddressString(“贛州”, completionHandler: {
(clpls:[CLPlacemark]?, error:Error?) -> Swift.Void in
if error != nil {return}
guard let sourceClpl = clpls?.first else{return}
self.geoc.geocodeAddressString(“廣州”, completionHandler: {clpls,error in
if error != nil {return}
guard let destinationCLPL = clpls?.first else {return}
self.requestDirections(sourceCLPL: sourceClpl, destCLPL: destinationCLPL)
})
})
}
func requestDirections(sourceCLPL:CLPlacemark,destCLPL:CLPlacemark) -> () {
//1笆载、創(chuàng)建路線請求對象(MKDirectionsRequest)
let directRequest = MKDirectionsRequest()
//2、設(shè)置路線請求的原始點(diǎn)(地理編碼)
let sourcePlace = MKPlacemark(placemark: sourceCLPL)
directRequest.source = MKMapItem(placemark: sourcePlace)
//3涯呻、設(shè)置路線請求的目的地點(diǎn)(地理編碼)
let destinationPlace = MKPlacemark(placemark: destCLPL)
directRequest.destination = MKMapItem(placemark: destinationPlace)
//4凉驻、創(chuàng)建路線規(guī)劃對象(MKDirections),去請求路線信息
let direction = MKDirections(request: directRequest)
//5复罐、計算路線信息
direction.calculate { (response:MKDirectionsResponse?, error:Error?) in
//6涝登、將計算出來的路線的折線覆蓋層模型數(shù)據(jù) 添加到地圖,觸發(fā)代理方法效诅,去設(shè)置覆蓋層的渲染圖層
//①根據(jù)響應(yīng)獲得路線
guard let route = response?.routes.first else{return}
//②根據(jù)路線獲得折線數(shù)據(jù)模型
let polyline = route.polyline
//③將折線數(shù)據(jù)模型添加到地圖(路線也是覆蓋層胀滚,只要添加了覆蓋層就會觸發(fā)代理方法去渲染圖層)
self.mapView.add(polyline)
}
}
//添加一個圓形覆蓋層
}
//MARK:- 代理方法中畫出線路
extension ViewController:MKMapViewDelegate{
/// 當(dāng)往地圖上添加覆蓋層,就會來到該方法,在此處創(chuàng)建該覆蓋層數(shù)據(jù)模型的渲染圖層
/// 注:不同覆蓋層數(shù)據(jù)模型,使用不同的渲染圖層,否則會崩潰
/// – Parameters:
/// – mapView: 地圖視圖
/// – overlay: 覆蓋層數(shù)據(jù)模型
/// – Returns: 覆蓋層渲染圖層
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
var render = MKOverlayPathRenderer()
//注:不同覆蓋層數(shù)據(jù)模型,使用不同的渲染圖層,否則會崩潰
if overlay.isKind(of: MKCircle.self){ //圓形圖層
//1、創(chuàng)建圓形渲染圖層
let circleRenderer = MKCircleRenderer(overlay: overlay)
//2乱投、設(shè)置顏色
circleRenderer.fillColor = UIColor.blue
circleRenderer.alpha = 0.3
render = circleRenderer
}else if overlay.isKind(of: MKPolyline.self) { //折線圖層
//1蛛淋、創(chuàng)建折線渲染圖層
let polylineRenderer = MKPolylineRenderer(overlay: overlay)
//2、設(shè)置線寬篡腌、顏色
polylineRenderer.lineWidth = 5
polylineRenderer.strokeColor = UIColor.green
render = polylineRenderer
}
return render
}
//當(dāng)用戶位置更新時
func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation) {
let coordinate = userLocation.coordinate
//添加圓形覆蓋層
let circle = MKCircle(center: coordinate, radius: 10000)
mapView.add(circle)
}
}
3、方案三:百度SDK
這個方法太占內(nèi)存勾效,不推薦使用嘹悼,建議了解,后面我封裝好了工具類层宫,只需要導(dǎo)入22個靜態(tài)庫就能很輕松使用
** 展示百度地圖步驟:
詳細(xì)步驟在百度的SDK的指南里說的很清楚杨伙,可以點(diǎn)進(jìn)這里來看看:http://lbsyun.baidu.com/index.php?title=iossdk/guide/buildproject**
- 展示地圖:
1、在百度地圖開發(fā)里申請密鑰萌腿,下載百度的SDK
2限匣、配置環(huán)境:引入mapapi.bundle資源文件時,需要拖BaiduMapAPI_Map.framework毁菱,和里面的Resources文件米死。需要什么功能就拖入什么靜態(tài)庫
3锌历、AppDelegate里面啟動BaiduMapManager,關(guān)注網(wǎng)絡(luò)和授權(quán)驗(yàn)證
4峦筒、通過BMKMapView創(chuàng)建地圖視圖究西,添加到控制器
** 此時能顯示地圖了 **
//1、初始化懶加載mapManager
lazy var mapManager:BMKMapManager = {
return BMKMapManager()
}()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// 1物喷、要使用百度地圖卤材,請先啟動BaiduMapManager
_ = mapManager
// 2、如果要關(guān)注網(wǎng)絡(luò)及授權(quán)驗(yàn)證事件峦失,請?jiān)O(shè)定 generalDelegate參數(shù)
let ret = mapManager.start(“wejGXHXM8vHPrjU9tL4gXSjqogYpto9H”, generalDelegate: self)
if ret == false {
print(“manager start failed!”);
}
return true
}
}
//MARK:- BMKGeneral代理
//點(diǎn)進(jìn)代理去扇丛,實(shí)現(xiàn)代理里面申明的2個方法
extension AppDelegate:BMKGeneralDelegate{
/**
*返回網(wǎng)絡(luò)錯誤
*@param iError 錯誤號
*/
func onGetNetworkState(_ iError:Int32){
if iError == 0 {
print(“網(wǎng)絡(luò)成功”)
}else{
print(“網(wǎng)絡(luò)失敗”)
}
}
/**
*返回授權(quán)驗(yàn)證錯誤
*@param iError 錯誤號 : 為0時驗(yàn)證通過,具體參加BMKPermissionCheckResultCode
*/
func onGetPermissionState(_ iError:Int32){ //這里要用Int32尉辑,不然跟代理方法里面申明的方法不一樣了
if iError == 0 {
print(“授權(quán)驗(yàn)證成功”)
}else{
print(“授權(quán)驗(yàn)證失敗”)
}
}
** 檢索帆精、添加大頭針步驟:**
添加頭文件:#import <BaiduMapAPI_Search/BMKSearchComponent.h>//引入檢索功能所有的頭文件
http://lbsyun.baidu.com/index.php?title=iossdk/guide/retrieval
根據(jù)網(wǎng)址的文檔上的步驟來:
1、創(chuàng)建檢索對象
2材蹬、發(fā)起檢索
3实幕、檢索成功回調(diào)代理方法
4、在代理方法中添加大頭針
** 導(dǎo)航堤器、定位 **
1昆庇、拖baiduNaviSDK.bundle資源包
2.把AudioToolbox.framework、ImageIO.framework闸溃、CoreMotion.framework整吆、CoreLocation.framework、CoreTelephony.framework辉川、MediaPlayer.framework表蝙、AVFoundation.framework、SystemConfiguration.framework乓旗、JavaScriptCore.framework府蛇、Security.framework 、OpenGLES.framework 屿愚、GLKit.framework 汇跨、libstdc++6.0.9.dylib、libsqlite3.0.tbd妆距、libz.1.2.5.tbd這幾個framework添加到工程中
3穷遂、“Other Linker Flags”添加“-ObjC” 標(biāo)識
4、info.plist文件中 設(shè)置 “Required background modes”娱据、 “App Transport Security Settings”蚪黑、 ”NSLocationAlwaysUsageDescription”、 ”NSLocationWhenInUseUsageDescription”、 ”View controller-based status bar appearance”忌穿、” LSApplicationQueriesSchemes”
5抒寂、添加頭文件:
#import“BNCoreServices.h”//導(dǎo)航
#import <BaiduMapAPI_Location/BMKLocationComponent.h>//引入定位功能所有的頭文件
** 注:此時編譯如果有報錯,則改用真機(jī)伴网,因?yàn)榘俣鹊腟DK有的版本的靜態(tài)庫不支持模擬機(jī)
否則你會一直報這個錯誤蓬推,連編譯都不成功**
6、在控制器里創(chuàng)建定位服務(wù)對象澡腾,開始定位沸伏,在其處理位置更新的代理方法里進(jìn)行發(fā)起路線規(guī)劃http://lbsyun.baidu.com/index.php?
7、發(fā)起路徑規(guī)劃進(jìn)行計算路徑动分,當(dāng)計算成功時會觸發(fā)回調(diào)代理方法毅糟,在這個代理方法里開始導(dǎo)航
** 注:如果提示無法TTS授權(quán),則檢查這幾個原因:1.項(xiàng)目名不能為中文澜公;2.沒有(注冊)開啟語音播報姆另; 3.必須使用真機(jī)**
注冊語音播報:http://lbsyun.baidu.com/index.php?title=ios-navsdk/guide/voice
封裝好的類具體代碼實(shí)現(xiàn):
import UIKit
class JGBaiDuMapTool: NSObject {
/// 提供單例
static let shareInstance = JGBaiDuMapTool()
/// 懶加載檢索對象
lazy var searcher:BMKPoiSearch = {
//初始化檢索對象
let searcher = BMKPoiSearch()
searcher.delegate = self
return searcher
}()
/// 創(chuàng)建 定位服務(wù)
lazy var locService:BMKLocationService = {
let locService = BMKLocationService()
locService.delegate = self
return locService
}()
/// 定義一個閉包,用來回調(diào)檢索結(jié)果
var poiResultBlock:(([BMKPoiInfo]) -> ())?
/// 添加大頭針
func addAnnotation(coordinate:CLLocationCoordinate2D,title:String,subtitle:String,mapView:BMKMapView) {
//創(chuàng)建大頭針
let annotion = BMKPointAnnotation()
//①通過poi列表計算經(jīng)緯度
annotion.coordinate = coordinate
//②通過poi列表拿到title
annotion.title = title
//③通過poi列表拿到subtitle
annotion.subtitle = subtitle
//④把大頭針添加到地圖
mapView.addAnnotation(annotion)
}
/// 發(fā)起檢索坟乾,觸發(fā)代理方法迹辐,通過閉包在代理方法里面保存到的結(jié)果,拿出去給外界用
func poiSearch(coordinate:CLLocationCoordinate2D,keyword:String,resultBlock:@escaping ([BMKPoiInfo]) -> ()){
//記錄block甚侣,用于外界在獲取到檢索結(jié)果時明吩,拿到結(jié)果做事情
self.poiResultBlock = resultBlock
//發(fā)起檢索
let option = BMKNearbySearchOption()
// 起始頁
option.pageIndex = 0
// 獲取多少
option.pageCapacity = 20
// 檢索的經(jīng)緯度
option.location = coordinate
// 檢索的關(guān)鍵字
option.keyword = keyword
// 開始檢索
let flag = searcher.poiSearchNear(by: option)
if flag == true{
print(“周邊檢索發(fā)送成功”);
}
else
{
print(“周邊檢索發(fā)送失敗”);
}
}
/// 開始導(dǎo)航
func beginNavigation(coordinate:CLLocationCoordinate2D) {
//節(jié)點(diǎn)數(shù)組
var nodesArray = [BNRoutePlanNode]()
//起點(diǎn)
let startNode = BNRoutePlanNode()
startNode.pos = BNPosition()
startNode.pos.x = coordinate.longitude
startNode.pos.y = coordinate.latitude
startNode.pos.eType = BNCoordinate_BaiduMapSDK;
nodesArray.append(startNode)
//終點(diǎn)
let endNode = BNRoutePlanNode()
endNode.pos = BNPosition()
endNode.pos.x = coordinate.longitude – 1
endNode.pos.y = coordinate.latitude
endNode.pos.eType = BNCoordinate_BaiduMapSDK
nodesArray.append(endNode)
//發(fā)起路徑規(guī)劃
BNCoreServices.routePlanService().startNaviRoutePlan(BNRoutePlanMode_Recommend, naviNodes: nodesArray, time: nil, delegete: self, userInfo: nil)
}
}
//MARK:- 檢索到結(jié)果回調(diào)
extension JGBaiDuMapTool:BMKPoiSearchDelegate{
//當(dāng)檢索到結(jié)果的時候,會來到這個方法
func onGetPoiResult(_ searcher: BMKPoiSearch!, result poiResult: BMKPoiResult!, errorCode: BMKSearchErrorCode) {
if errorCode == BMK_SEARCH_NO_ERROR {
//在此處理正常結(jié)果
guard let poiLists = poiResult.poiInfoList as? [BMKPoiInfo] else{return} //檢索到的結(jié)果很多是一個數(shù)組
//將檢索獲取到的結(jié)果殷费,保存在閉包里面
self.poiResultBlock!(poiLists)
}
else if errorCode == BMK_SEARCH_AMBIGUOUS_KEYWORD{
//當(dāng)在設(shè)置城市未找到結(jié)果印荔,但在其他城市找到結(jié)果時,回調(diào)建議檢索城市列表
// result.cityList;
print(“起始點(diǎn)有歧義”)
} else {
print(“抱歉详羡,未找到結(jié)果”)
}
}
}
//MARK:- 導(dǎo)航代理
extension JGBaiDuMapTool:BNNaviRoutePlanDelegate{
//算路成功回調(diào)
func routePlanDidFinished(_ userInfo: [AnyHashable : Any]!) {
//路徑規(guī)劃成功仍律,開始導(dǎo)航
//BNaviUI_NormalNavi, //正常導(dǎo)航
//BNaviUI_Declaration, //聲明頁面
BNCoreServices.uiService().showPage(BNaviUI_NormalNavi, delegate: nil, extParams: nil)
}
}
//MARK:- 定位代理
extension JGBaiDuMapTool:BMKLocationServiceDelegate{
//實(shí)現(xiàn)相關(guān)delegate 處理位置信息更新
//處理方向變更信息
func didUpdateUserHeading(_ userLocation: BMKUserLocation!) {
self.beginNavigation(coordinate: userLocation.location.coordinate)
}
}