版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2018.10.14 星期日 |
前言
MapKit框架直接從您的應(yīng)用界面顯示地圖或衛(wèi)星圖像馋记,調(diào)出興趣點腻窒,并確定地圖坐標的地標信息滩字。接下來幾篇我們就一起看一下這個框架。感興趣的看下面幾篇文章御吞。
1. MapKit框架詳細解析(一) —— 基本概覽(一)
2. MapKit框架詳細解析(二) —— 基本使用簡單示例(一)
3. MapKit框架詳細解析(三) —— 基本使用簡單示例(二)
開始
首先看一下寫作環(huán)境
Swift 4, iOS 11, Xcode 9
本篇主要就是了解如何使用MapKit疊加視圖將衛(wèi)星和混合地圖,自定義圖像,注釋夜涕,線條蒋譬,邊界和圓圈添加到標準地圖。
Apple可以很容易地使用MapKit
向您的應(yīng)用添加地圖揍诽,但僅此一點并不十分吸引人诀蓉。 幸運的是,您可以使用custom overlay views
使地圖更具吸引力暑脆。
在這個MapKit
教程中渠啤,您將創(chuàng)建一個應(yīng)用程序來展示Six Flags Magic Mountain。 對于你那里快速騎行的刺激尋求者添吗,這個應(yīng)用程序適合你沥曹。
當您完成時,您將擁有一個交互式公園地圖碟联,顯示景點位置妓美,騎行路線和角色位置。
打開入門項目鲤孵。 此啟動包含導(dǎo)航壶栋,但它還沒有任何地圖。
在Xcode中打開啟動項目普监;Build和運行贵试;你會看到一個空白的視圖琉兜。 您很快就會在此處添加地圖和可選擇的疊加層類型。
Adding a MapView with MapKit - 使用MapKit添加MapView
打開Main.storyboard
并選擇Park Map View Controller
場景锡移。 在Object Library
中搜索map
呕童,然后將Map View
拖放到此場景中。 將其放置在導(dǎo)航欄下方淆珊,使其填充視圖的其余部分夺饲。
接下來,選擇Add New Constraints
按鈕施符,使用常量0添加四個約束往声,然后單擊Add 4 Constraints
。
1. Wiring Up the MapView - 連接MapView
要對MapView
執(zhí)行任何有用的操作戳吝,您需要做兩件事:(1)為其設(shè)置outlet
浩销,以及(2)設(shè)置其代理。
通過按住Option
鍵并在文件層次結(jié)構(gòu)中左鍵單擊ParkMapViewController.swift
听哭,在Assistant Editor
中打開ParkMapViewController
慢洋。
然后,從map view
按住control拖動到第一個方法的正上方陆盘,如下所示:
在出現(xiàn)的彈出窗口中普筹,將outlet
命名為mapView
,然后單擊Connect
隘马。
要設(shè)置地圖視圖的代理太防,請右鍵單擊地圖視圖對象以打開其上下文菜單,然后從代理outlet
拖動到Park Map View Controller
酸员,如下所示:
您還需要使ParkMapViewController
符合MKMapViewDelegate
蜒车。
首先,將此import添加到ParkMapViewController.swift
的頂部:
import MapKit
然后幔嗦,在結(jié)束類花括號之后添加此擴展:
extension ParkMapViewController: MKMapViewDelegate {
}
Build并運行以查看新地圖酿愧!
2. Interacting with the MapView - 與MapView交互
您將首先將地圖置于公園中心。 在應(yīng)用程序的Park Information
文件夾中邀泉,您將找到名為MagicMountain.plist
的文件寓娩。 打開此文件,您將看到它包含公園中點和邊界信息的坐標呼渣。
您現(xiàn)在將為此plist創(chuàng)建一個模型棘伴,以便在應(yīng)用程序中輕松使用。
右鍵單擊文件導(dǎo)航中的Models
組屁置,然后選擇New File ...
焊夸,選擇iOS \ Source \ Swift File
模板并將其命名為Park.swift
。 用以下內(nèi)容替換其內(nèi)容:
import UIKit
import MapKit
class Park {
var name: String?
var boundary: [CLLocationCoordinate2D] = []
var midCoordinate = CLLocationCoordinate2D()
var overlayTopLeftCoordinate = CLLocationCoordinate2D()
var overlayTopRightCoordinate = CLLocationCoordinate2D()
var overlayBottomLeftCoordinate = CLLocationCoordinate2D()
var overlayBottomRightCoordinate = CLLocationCoordinate2D()
var overlayBoundingMapRect: MKMapRect?
}
您還需要能夠?qū)?code>Park的值設(shè)置為plist中定義的值蓝角。
首先阱穗,添加此便捷方法以反序列化屬性列表:
class func plist(_ plist: String) -> Any? {
let filePath = Bundle.main.path(forResource: plist, ofType: "plist")!
let data = FileManager.default.contents(atPath: filePath)!
return try! PropertyListSerialization.propertyList(from: data, options: [], format: nil)
}
接下來饭冬,在給定fieldName
和字典的情況下,添加下一個方法來解析CLLocationCoordinate2D
:
static func parseCoord(dict: [String: Any], fieldName: String) -> CLLocationCoordinate2D {
guard let coord = dict[fieldName] as? String else {
return CLLocationCoordinate2D()
}
let point = CGPointFromString(coord)
return CLLocationCoordinate2DMake(CLLocationDegrees(point.x), CLLocationDegrees(point.y))
}
MapKit的API使用CLLocationCoordinate2D
來表示地理位置揪阶。
您現(xiàn)在終于準備為此類創(chuàng)建初始化程序:
init(filename: String) {
guard let properties = Park.plist(filename) as? [String : Any],
let boundaryPoints = properties["boundary"] as? [String] else { return }
midCoordinate = Park.parseCoord(dict: properties, fieldName: "midCoord")
overlayTopLeftCoordinate = Park.parseCoord(dict: properties, fieldName: "overlayTopLeftCoord")
overlayTopRightCoordinate = Park.parseCoord(dict: properties, fieldName: "overlayTopRightCoord")
overlayBottomLeftCoordinate = Park.parseCoord(dict: properties, fieldName: "overlayBottomLeftCoord")
let cgPoints = boundaryPoints.map { CGPointFromString($0) }
boundary = cgPoints.map { CLLocationCoordinate2DMake(CLLocationDegrees($0.x), CLLocationDegrees($0.y)) }
}
首先昌抠,從plist文件中提取公園的坐標并將其分配給屬性。 然后設(shè)置boundary
數(shù)組鲁僚,稍后您將使用它來顯示公園輪廓炊苫。
您可能想知道,“為什么沒有從plist設(shè)置overlayBottomRightCoordinate
冰沙?”這在plist中沒有提供侨艾,因為您可以從其他三個點輕松計算它。
用這個計算屬性替換當前的overlayBottomRightCoordinate
:
var overlayBottomRightCoordinate: CLLocationCoordinate2D {
get {
return CLLocationCoordinate2DMake(overlayBottomLeftCoordinate.latitude,
overlayTopRightCoordinate.longitude)
}
}
最后拓挥,您需要一種方法來基于疊加坐標創(chuàng)建邊界框唠梨。
用這個替換overlayBoundingMapRect
的定義:
var overlayBoundingMapRect: MKMapRect {
get {
let topLeft = MKMapPointForCoordinate(overlayTopLeftCoordinate)
let topRight = MKMapPointForCoordinate(overlayTopRightCoordinate)
let bottomLeft = MKMapPointForCoordinate(overlayBottomLeftCoordinate)
return MKMapRectMake(
topLeft.x,
topLeft.y,
fabs(topLeft.x - topRight.x),
fabs(topLeft.y - bottomLeft.y))
}
}
此getter
為公園的邊界生成MKMapRect
對象。 這只是一個矩形侥啤,它定義了公園的大小当叭,以公園的中點為中心。
現(xiàn)在是時候讓這個類使用了盖灸。 打開ParkMapViewController.swift
并向其添加以下屬性:
var park = Park(filename: "MagicMountain")
然后科展,用這個替換viewDidLoad()
:
override func viewDidLoad() {
super.viewDidLoad()
let latDelta = park.overlayTopLeftCoordinate.latitude -
park.overlayBottomRightCoordinate.latitude
// Think of a span as a tv size, measure from one corner to another
let span = MKCoordinateSpanMake(fabs(latDelta), 0.0)
let region = MKCoordinateRegionMake(park.midCoordinate, span)
mapView.region = region
}
這將創(chuàng)建一個緯度增量,即從公園的左上角坐標到公園的右下角坐標的距離糠雨。 您可以使用它來生成MKCoordinateSpan
,它定義了地圖區(qū)域所跨越的區(qū)域徘跪。 然后使用MKCoordinateSpan
和公園的midCoordinate
創(chuàng)建一個MKCoordinateRegion
甘邀,將公園定位在地圖視圖上。
Build并運行您的應(yīng)用程序垮庐,您將看到地圖現(xiàn)在以Six Flags Magic Mountain
為中心松邪!
好的! 你把地圖集中在以公園為中心哨查,這很不錯逗抑,但并不是非常令人興奮。 讓我們通過將地圖類型切換為衛(wèi)星來增添趣味寒亥!
Switching The Map Type - 切換地圖類型
在ParkMapViewController.swift
中邮府,您會注意到這個方法:
@IBAction func mapTypeChanged(_ sender: UISegmentedControl) {
// TODO
}
入門項目有很多你需要做的來充實這個方法。 您是否注意到位于地圖視圖上方的segmented control
似乎做了很多事情溉奕?
segmented control
實際上是調(diào)用mapTypeChanged(_ :)
褂傀,但正如你在上面看到的,這個方法什么也沒做加勤!
將以下實現(xiàn)添加到mapTypeChanged()
:
mapView.mapType = MKMapType.init(rawValue: UInt(sender.selectedSegmentIndex)) ?? .standard
信不信由你仙辟,在您的應(yīng)用中添加標準同波,衛(wèi)星和混合地圖類型就像上面的代碼一樣簡單! 那不容易嗎叠国?
Build并運行未檩,并嘗試分段控件來更改地圖類型!
即使衛(wèi)星視圖仍然比標準地圖視圖好得多粟焊,它對您的公園訪客仍然沒有多大幫助冤狡。 沒有任何標簽 - 您的用戶將如何在公園內(nèi)找到任何東西?
一個顯而易見的方法是將UIView
放在地圖視圖的頂部吆玖,但是你可以更進一步筒溃,而是利用MKOverlayRenderer
的魔力為你做很多工作!
All About Overlay Views - 所有關(guān)于疊加視圖
在開始創(chuàng)建自己的疊加視圖之前沾乘,您需要了解兩個關(guān)鍵類:MKOverlay
和MKOverlayRenderer
怜奖。
MKOverlay
告訴MapKit
你想要繪制疊加層的位置。使用該類有三個步驟:
- 1) 創(chuàng)建自己的實現(xiàn)MKOverlay protocol協(xié)議的自定義類翅阵,該協(xié)議具有兩個必需屬性:
coordinate
和boundingMapRect
歪玲。這些屬性定義了疊加層在地圖上的位置以及疊加層的大小。 - 2) 為要顯示疊加層的每個區(qū)域創(chuàng)建類的實例掷匠。例如滥崩,在這個應(yīng)用程序中,您可以為過山車覆蓋層創(chuàng)建一個實例讹语,為餐廳覆蓋層創(chuàng)建另一個實例钙皮。
- 3) 最后,將疊加層添加到地圖視圖中顽决。
現(xiàn)在短条,地圖視圖知道它應(yīng)該顯示疊加的位置,但它如何知道每個區(qū)域中顯示的內(nèi)容才菠?
輸入MKOverlayRenderer
茸时。您將其子類化以設(shè)置要在每個點中顯示的內(nèi)容。例如赋访,在這個應(yīng)用程序中可都,您將繪制過山車或餐廳的圖像。
MKOverlayRenderer
實際上只是一種特殊的UIView
蚓耽,因為它繼承自UIView渠牲。但是,您不應(yīng)將MKOverlayRenderer
直接添加到MKMapView
步悠。相反嘱兼,MapKit
希望這是一個MKMapView
。
還記得你之前設(shè)置的地圖視圖代理嗎贤徒?有一個代理方法芹壕,允許您返回疊加視圖:
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer
當MapKit意識到地圖視圖正在顯示的區(qū)域中有一個MKOverlay
對象時汇四,它將調(diào)用此方法。
總結(jié)一下踢涌,不要將MKOverlayRenderer
對象直接添加到地圖視圖中通孽;相反,您告訴地圖有關(guān)MKOverlay
對象的顯示睁壁,并在代理方法請求它們時返回它們背苦。
既然您已經(jīng)了解了這個理論,那么現(xiàn)在是時候使用這些概念了潘明!
Adding Your Own Information - 添加自己的信息
如前所述行剂,衛(wèi)星視圖仍未提供有關(guān)公園的足夠信息。 您的任務(wù)是創(chuàng)建一個表示整個公園的疊加層的對象钳降。
選擇Overlays
組并創(chuàng)建一個名為ParkMapOverlay.swift
的新Swift文件厚宰。 用以下內(nèi)容替換其內(nèi)容:
import UIKit
import MapKit
class ParkMapOverlay: NSObject, MKOverlay {
var coordinate: CLLocationCoordinate2D
var boundingMapRect: MKMapRect
init(park: Park) {
boundingMapRect = park.overlayBoundingMapRect
coordinate = park.midCoordinate
}
}
遵循MKOverlay
意味著您還必須繼承NSObject
。 最后遂填,初始化程序只從傳遞的Park
對象中獲取屬性铲觉,并將它們設(shè)置為相應(yīng)的MKOverlay
屬性。
現(xiàn)在吓坚,您需要創(chuàng)建一個從MKOverlayRenderer
類派生的視圖類撵幽。
在Overlays
組中創(chuàng)建一個名為ParkMapOverlayView.swift
的新Swift文件。 用以下內(nèi)容替換其內(nèi)容:
import UIKit
import MapKit
class ParkMapOverlayView: MKOverlayRenderer {
var overlayImage: UIImage
init(overlay:MKOverlay, overlayImage:UIImage) {
self.overlayImage = overlayImage
super.init(overlay: overlay)
}
override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) {
guard let imageReference = overlayImage.cgImage else { return }
let rect = self.rect(for: overlay.boundingMapRect)
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: 0.0, y: -rect.size.height)
context.draw(imageReference, in: rect)
}
}
init(overlay:overlayImage :)
通過提供第二個參數(shù)有效地覆蓋了基本方法init(overlay :)
礁击。
draw
是這堂課的真正做東西的地方盐杂。 它定義了MapKit
在給定特定的MKMapRect
,MKZoomScale
和圖形上下文的CGContext
時應(yīng)如何呈現(xiàn)此視圖哆窿,以便以適當?shù)谋壤龑B加圖像繪制到上下文中链烈。
Core Graphics繪圖的詳細信息遠遠超出了本教程的范圍。 但是更耻,您可以看到上面的代碼使用傳遞的MKMapRect
來獲取CGRect
,以便確定在提供的上下文中繪制UIImage
的CGImage
的位置捏膨。
現(xiàn)在您已同時擁有MKOverlay
和MKOverlayRenderer
秧均,您可以將它們添加到地圖視圖中。
在ParkMapViewController.swift
中号涯,將以下方法添加到類中:
func addOverlay() {
let overlay = ParkMapOverlay(park: park)
mapView.add(overlay)
}
此方法將MKOverlay
添加到地圖視圖中目胡。
如果用戶應(yīng)選擇顯示地圖疊加層,則loadSelectedOptions()
應(yīng)調(diào)用addOverlay()
链快。 使用以下代碼替換loadSelectedOptions()
:
func loadSelectedOptions() {
mapView.removeAnnotations(mapView.annotations)
mapView.removeOverlays(mapView.overlays)
for option in selectedOptions {
switch (option) {
case .mapOverlay:
addOverlay()
default:
break;
}
}
}
每當用戶關(guān)閉選項選擇視圖時誉己,應(yīng)用程序調(diào)用loadSelectedOptions()
,然后確定所選選項域蜗,并調(diào)用適當?shù)姆椒ㄔ诘貓D視圖上呈現(xiàn)這些選擇巨双。
loadSelectedOptions()
還會刪除可能存在的任何annotations
和overlays
噪猾,以便您不會最終出現(xiàn)重復(fù)的渲染。 這不一定有效筑累,但它是從地圖中清除先前項目的簡單方法袱蜡。
要實現(xiàn)代理方法,請將以下方法添加到文件底部的MKMapViewDelegate
擴展中:
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if overlay is ParkMapOverlay {
return ParkMapOverlayView(overlay: overlay, overlayImage: #imageLiteral(resourceName: "overlay_park"))
}
return MKOverlayRenderer()
}
當應(yīng)用程序確定MKOverlay
在視圖中時慢宗,地圖視圖將上述方法作為委托調(diào)用坪蚁。
在這里,您檢查疊加層是否屬于類型ParkMapOverlay
镜沽。 如果是這樣敏晤,則加載疊加圖像,使用疊加圖像創(chuàng)建ParkMapOverlayView
實例缅茉,并將此實例返回給調(diào)用者嘴脾。
但是有一個小問題 - 那可疑的小overlay_park
圖片來自哪里?
這是一個PNG文件宾舅,其目的是覆蓋公園邊界的地圖視圖统阿。 overlay_park
圖像(在image assets
中找到)如下所示:
Build并運行,選擇Map Overlay
選項筹我,瞧扶平! 在地圖上方繪制了公園覆蓋圖:
根據(jù)需要放大,縮小和移動 - 疊加視圖按照您的預(yù)期進行縮放和移動蔬蕊。
后記
本篇主要講述了一個疊加視圖相關(guān)的簡單示例结澄,感興趣的給個贊或者關(guān)注~~~