MapKit框架詳細解析(四) —— 一個疊加視圖相關(guān)的簡單示例(一)

版本記錄

版本號 時間
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)鍵類:MKOverlayMKOverlayRenderer怜奖。

MKOverlay告訴MapKit你想要繪制疊加層的位置。使用該類有三個步驟:

  • 1) 創(chuàng)建自己的實現(xiàn)MKOverlay protocol協(xié)議的自定義類翅阵,該協(xié)議具有兩個必需屬性:coordinateboundingMapRect歪玲。這些屬性定義了疊加層在地圖上的位置以及疊加層的大小。
  • 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在給定特定的MKMapRectMKZoomScale和圖形上下文的CGContext時應(yīng)如何呈現(xiàn)此視圖哆窿,以便以適當?shù)谋壤龑B加圖像繪制到上下文中链烈。

Core Graphics繪圖的詳細信息遠遠超出了本教程的范圍。 但是更耻,您可以看到上面的代碼使用傳遞的MKMapRect來獲取CGRect,以便確定在提供的上下文中繪制UIImageCGImage的位置捏膨。

現(xiàn)在您已同時擁有MKOverlayMKOverlayRenderer秧均,您可以將它們添加到地圖視圖中。

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()還會刪除可能存在的任何annotationsoverlays噪猾,以便您不會最終出現(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)注~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市岸夯,隨后出現(xiàn)的幾起案子麻献,更是在濱河造成了極大的恐慌,老刑警劉巖猜扮,帶你破解...
    沈念sama閱讀 211,561評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件勉吻,死亡現(xiàn)場離奇詭異,居然都是意外死亡旅赢,警方通過查閱死者的電腦和手機齿桃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來煮盼,“玉大人短纵,你說我怎么就攤上這事〗┛兀” “怎么了香到?”我有些...
    開封第一講書人閱讀 157,162評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我悠就,道長千绪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,470評論 1 283
  • 正文 為了忘掉前任理卑,我火速辦了婚禮翘紊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘藐唠。我一直安慰自己帆疟,他們只是感情好,可當我...
    茶點故事閱讀 65,550評論 6 385
  • 文/花漫 我一把揭開白布宇立。 她就那樣靜靜地躺著踪宠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪妈嘹。 梳的紋絲不亂的頭發(fā)上柳琢,一...
    開封第一講書人閱讀 49,806評論 1 290
  • 那天,我揣著相機與錄音润脸,去河邊找鬼柬脸。 笑死,一個胖子當著我的面吹牛毙驯,可吹牛的內(nèi)容都是我干的倒堕。 我是一名探鬼主播,決...
    沈念sama閱讀 38,951評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼爆价,長吁一口氣:“原來是場噩夢啊……” “哼垦巴!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起铭段,我...
    開封第一講書人閱讀 37,712評論 0 266
  • 序言:老撾萬榮一對情侶失蹤骤宣,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后序愚,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體憔披,經(jīng)...
    沈念sama閱讀 44,166評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,510評論 2 327
  • 正文 我和宋清朗相戀三年爸吮,在試婚紗的時候發(fā)現(xiàn)自己被綠了芬膝。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,643評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡拗胜,死狀恐怖蔗候,靈堂內(nèi)的尸體忽然破棺而出怒允,到底是詐尸還是另有隱情埂软,我是刑警寧澤,帶...
    沈念sama閱讀 34,306評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站勘畔,受9級特大地震影響所灸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜炫七,卻給世界環(huán)境...
    茶點故事閱讀 39,930評論 3 313
  • 文/蒙蒙 一爬立、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧万哪,春花似錦侠驯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至的止,卻和暖如春檩坚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背诅福。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評論 1 266
  • 我被黑心中介騙來泰國打工匾委, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人氓润。 一個月前我還...
    沈念sama閱讀 46,351評論 2 360
  • 正文 我出身青樓赂乐,卻偏偏與公主長得像,于是被迫代替她去往敵國和親旺芽。 傳聞我的和親對象是個殘疾皇子沪猴,可洞房花燭夜當晚...
    茶點故事閱讀 43,509評論 2 348

推薦閱讀更多精彩內(nèi)容