MapKit框架詳細解析(五) —— 一個疊加視圖相關的簡單示例(二)

版本記錄

版本號 時間
V1.0 2018.10.14 星期日

前言

MapKit框架直接從您的應用界面顯示地圖或衛星圖像,調出興趣點,并確定地圖坐標的地標信息。接下來幾篇我們就一起看一下這個框架。感興趣的看下面幾篇文章。
1. MapKit框架詳細解析(一) —— 基本概覽(一)
2. MapKit框架詳細解析(二) —— 基本使用簡單示例(一)
3. MapKit框架詳細解析(三) —— 基本使用簡單示例(二)
4. MapKit框架詳細解析(四) —— 一個疊加視圖相關的簡單示例(一)

Annotations - 注釋

如果您曾在地圖應用中搜索過某個位置,那么您已經看到了地圖上顯示的那些彩色pins。 這些稱為annotations,使用MKAnnotationView創建。 您可以在自己的應用程序中使用annotations - 您可以使用您想要的任何圖像,而不僅僅是pins

在您的應用中,annotations將非常有用,可以幫助指出公園游客的特定景點。 annotations對象與MKOverlayMKOverlayRenderer的工作方式類似,但您將使用MKAnnotationMKAnnotationView

Annotationations組中創建一個名為AttractionAnnotation.swift的新Swift文件。 用以下內容替換其內容:

import UIKit
import MapKit

enum AttractionType: Int {
  case misc = 0
  case ride
  case food
  case firstAid
  
  func image() -> UIImage {
    switch self {
    case .misc:
      return #imageLiteral(resourceName: "star")
    case .ride:
      return #imageLiteral(resourceName: "ride")
    case .food:
      return #imageLiteral(resourceName: "food")
    case .firstAid:
      return #imageLiteral(resourceName: "firstaid")
    }
  }
}

class AttractionAnnotation: NSObject, MKAnnotation {
  var coordinate: CLLocationCoordinate2D
  var title: String?
  var subtitle: String?
  var type: AttractionType
  
  init(coordinate: CLLocationCoordinate2D, title: String, subtitle: String, type: AttractionType) {
    self.coordinate = coordinate
    self.title = title
    self.subtitle = subtitle
    self.type = type
  }
}

在這里,您首先為AttractionType定義一個枚舉,以幫助您將每個吸引力分類為一個類型。 這個枚舉列出了四種類型的注釋:misc, rides, foods and first aid。 另外還有一個方便的函數來獲取正確的annotation圖像。

接下來,您聲明此類符合MKAnnotation Protocol。 與MKOverlay非常相似,MKAnnotation具有必需的coordinate屬性。 您可以定義特定于此實現的少數屬性。 最后,定義一個初始化程序,允許您為每個屬性賦值。

現在,您需要創建一個特定的MKAnnotation實例以用于annotations

Annotations組下創建另一個名為AttractionAnnotationView.swift的Swift文件。 用以下內容替換其內容:

import UIKit
import MapKit

class AttractionAnnotationView: MKAnnotationView {
  // Required for MKAnnotationView
  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
  }
  
  override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
    super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
    guard let attractionAnnotation = self.annotation as? AttractionAnnotation else { return }
    
    image = attractionAnnotation.type.image()
  }
}

MKAnnotationView需要init(coder :)初始化程序。 如果沒有它的定義,錯誤將阻止您構建和運行應用程序。 為了防止這種情況,只需定義它并調用其超類初始化程序。 在這里,您還可以根據annotationtype屬性覆蓋init(annotation:reuseIdentifier :),在annotationimage屬性上設置不同的圖像。

現在創建了annotation及其關聯視圖,您可以開始將它們添加到地圖視圖中!

要確定每個annotation的位置,您將使用MagicMountainAttractions.plist文件中的信息,您可以在Park Information組下找到該文件。 plist文件包含坐標信息和有關公園景點的其他詳細信息。

返回ParkMapViewController.swift并插入以下方法:

func addAttractionPins() {
  guard let attractions = Park.plist("MagicMountainAttractions") as? [[String : String]] else { return }
    
  for attraction in attractions {
    let coordinate = Park.parseCoord(dict: attraction, fieldName: "location")
    let title = attraction["name"] ?? ""
    let typeRawValue = Int(attraction["type"] ?? "0") ?? 0
    let type = AttractionType(rawValue: typeRawValue) ?? .misc
    let subtitle = attraction["subtitle"] ?? ""
    let annotation = AttractionAnnotation(coordinate: coordinate, title: title, subtitle: subtitle, type: type)
    mapView.addAnnotation(annotation)
  }
}

此方法讀取MagicMountainAttractions.plist并枚舉字典數組。 對于每個條目,它使用attraction的信息創建AttractionAnnotation的實例,然后將每個annotation添加到地圖視圖中。

現在,您需要更新loadSelectedOptions()以適應此新選項,并在用戶選擇它時執行新方法。

更新loadSelectedOptions()中的switch語句以包含以下內容:

case .mapPins:
  addAttractionPins()

這會在需要時調用新的addAttractionPins()方法。 請注意,對removeOverlays的調用也會隱藏pins覆蓋。

已經完成的差不多了! 最后但并非最不重要的是,您需要實現另一個代理方法,該方法將MKAnnotationView實例提供給地圖視圖,以便它可以自己呈現它們。

將以下方法添加到文件底部的MKMapViewDelegate類擴展中:

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
  let annotationView = AttractionAnnotationView(annotation: annotation, reuseIdentifier: "Attraction")
  annotationView.canShowCallout = true
  return annotationView
}

此方法接收選定的MKAnnotation并使用它來創建AttractionAnnotationView。 由于屬性canShowCallout設置為true,因此當用戶觸摸annotation時將顯示標注。 最后,該方法返回annotation view

Build并運行以查看您的annotation

打開Attraction Pins以查看結果,如下面的屏幕截圖所示:

此時吸引針看起來相當“尖銳”!

到目前為止,您已經介紹了很多復雜的MapKit,包括overlays and annotations。 但是如果你需要使用一些繪圖基元,比如線條,形狀和圓圈呢?

MapKit框架還使您能夠直接在地圖視圖上繪制。 MapKit就是為此目的提供MKPolylineMKPolygonMKCircle


I Walk The Line – MKPolyline - 我走線 - MKPolyline

如果你去過Magic Mountain,你就會知道Goliath過山車是一次令人難以置信的騎行,有些騎手喜歡在走進大門后直奔它!

為了幫助這些騎手,您將繪制從公園入口到Goliath的路徑。

MKPolyline是繪制連接多個點的路徑的絕佳解決方案,例如繪制從A點到B點的非線性路線。

要繪制折線,您需要按照代碼繪制它們的順序設置一系列經度和緯度坐標。

EntranceToGoliathRoute.plist(再次在Park Information文件夾中找到)包含路徑信息。

您需要一種方法來讀取該plist文件并為騎手創建路線。

打開ParkMapViewController.swift并將以下方法添加到類中:

func addRoute() {
  guard let points = Park.plist("EntranceToGoliathRoute") as? [String] else { return }
    
  let cgPoints = points.map { CGPointFromString($0) }
  let coords = cgPoints.map { CLLocationCoordinate2DMake(CLLocationDegrees($0.x), CLLocationDegrees($0.y)) }
  let myPolyline = MKPolyline(coordinates: coords, count: coords.count)
    
  mapView.add(myPolyline)
}

此方法讀取EntranceToGoliathRoute.plist,并將各個坐標字符串轉換為CLLocationCoordinate2D結構。

在您的應用中實現折線非常簡單;你只需創建一個包含所有點的數組,并將其傳遞給MKPolyline! 不會比這更簡單了吧。

現在,您需要添加一個選項,以允許用戶打開或關閉折線路徑。

更新loadSelectedOptions()以包含另一個case語句:

case .mapRoute:
  addRoute()

這在需要時調用addRoute()方法。

最后,要將它們組合在一起,您需要更新代理方法,以便它返回您要在地圖視圖上呈現的實際視圖。

用這個替換mapView(_:rendererForOverlay)

func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
  if overlay is ParkMapOverlay {
    return ParkMapOverlayView(overlay: overlay, overlayImage: #imageLiteral(resourceName: "overlay_park"))
  } else if overlay is MKPolyline {
    let lineView = MKPolylineRenderer(overlay: overlay)
    lineView.strokeColor = UIColor.green
    return lineView
  }
    
  return MKOverlayRenderer()
}

這里的更改是查找MKPolyline對象的新增的else if分支。 顯示折線視圖的過程與先前的疊加視圖非常相似。 但是,在這種情況下,您不需要創建任何自定義視圖對象。 您只需使用提供的MKPolyLineRenderer框架,并使用疊加層初始化新實例。

MKPolyLineRenderer還使您能夠更改折線的某些屬性。 在這種情況下,您已修改筆畫的顏色以顯示為綠色。

Build并運行您的應用程序,啟用Route選項,它將顯示在屏幕上:

Goliath狂熱分子現在可以在快速的時間內登上過山車!

向公園顧客展示實際公園邊界是很好的,因為公園實際上并沒有占據屏幕上顯示的整個空間。

雖然您可以使用MKPolyline在公園邊界周圍繪制形狀,但MapKit提供了另一個專門用于繪制閉合多邊形的類:MKPolygon


Don’t Fence Me In – MKPolygon - 不要圍困我 - MKPolygon

MKPolygonMKPolyline非常相似,只是坐標集中的第一個和最后一個點相互連接以創建閉合形狀。

您將創建一個MKPolygon作為顯示公園邊界的疊加層。 公園邊界坐標已在MagicMountain.plist中定義;返回并查看init(filename :)以查看plist文件中讀取邊界點的位置。

將以下方法添加到ParkMapViewController.swift

func addBoundary() {
  mapView.add(MKPolygon(coordinates: park.boundary, count: park.boundary.count))
}

上面的addBoundary()的實現非常簡單。 給定park實例的邊界數組和點數,您可以快速輕松地創建新的MKPolygon實例!

你能猜到下一步嗎? 這與你上面為MKPolyline所做的非常相似。

是的,這是正確的 - 在loadSelectedOptionsswitch中插入另一個case來處理顯示或隱藏公園邊界的新選項:

case .mapBoundary:
  addBoundary()

正如MKPolyline一樣,MKPolygon符合MKOverlay,因此您需要再次更新委托方法。

更新ParkMapViewController.swift中的委托方法,如下所示:

func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
  if overlay is ParkMapOverlay {
    return ParkMapOverlayView(overlay: overlay, overlayImage: #imageLiteral(resourceName: "overlay_park"))
  } else if overlay is MKPolyline {
    let lineView = MKPolylineRenderer(overlay: overlay)
    lineView.strokeColor = UIColor.green
    return lineView
  } else if overlay is MKPolygon {
    let polygonView = MKPolygonRenderer(overlay: overlay)
    polygonView.strokeColor = UIColor.magenta
    return polygonView
  }
    
  return MKOverlayRenderer()
}

代理方法的更新與以前一樣簡單。 您將MKOverlayView創建為MKPolygonRenderer的實例,并將筆觸顏色設置為洋紅色。

運行應用程序以查看您的新邊界:

這照顧了折線和多邊形。 要講述的最后一種繪圖方法是繪制圓圈作為疊加層,由MKCircle巧妙地處理。


Circle In The Sand – MKCircle - 在沙子圈 - MKCircle

MKCircle也非常類似于MKPolylineMKPolygon,除了它繪制一個圓,給定一個坐標點作為圓的中心,以及一個確定圓的大小的半徑。

標記公園角色的一般位置會很棒。 在地圖上繪制一些圓圈以模擬這些角色的位置!
MKCircle疊加層是實現此功能的一種非常簡單的方法。

Park Information文件夾還包含字符位置文件。 每個文件都是用戶角色的幾個坐標的數組。

在名為Models組下創建一個新的Swift文件Character.swift。 用以下代碼替換其內容:

import UIKit
import MapKit

class Character: MKCircle {
  
  var name: String?
  var color: UIColor?
  
  convenience init(filename: String, color: UIColor) {
    guard let points = Park.plist(filename) as? [String] else { self.init(); return }
    
    let cgPoints = points.map { CGPointFromString($0) }
    let coords = cgPoints.map { CLLocationCoordinate2DMake(CLLocationDegrees($0.x), CLLocationDegrees($0.y)) }
    
    let randomCenter = coords[Int(arc4random()%4)]
    let randomRadius = CLLocationDistance(max(5, Int(arc4random()%40)))
    
    self.init(center: randomCenter, radius: randomRadius)
    self.name = filename
    self.color = color
  }
}

您剛添加的新類符合MKCircle協議,并定義了兩個可選屬性:namecolor。 便捷初始化程序接受plist文件名和顏色來繪制圓。 然后,它從plist文件中讀取數據,并從文件中的四個位置中選擇一個隨機位置。 接下來,它選擇一個隨機半徑來模擬時間方差。 返回的MKCircle已設置好并準備好放在地圖上!

現在您需要一種方法來添加每個角色。 打開ParkMapViewController.swift并將以下方法添加到類中:

func addCharacterLocation() {
  mapView.add(Character(filename: "BatmanLocations", color: .blue))
  mapView.add(Character(filename: "TazLocations", color: .orange))
  mapView.add(Character(filename: "TweetyBirdLocations", color: .yellow))
}

上述方法幾乎對每個角色執行相同的操作。 它會傳遞每個文件的plist文件名,決定顏色并將其作為疊加層添加到地圖中。

你幾乎完成! 你能回憶起最后幾步應該是什么嗎?

是的,你仍然需要提供一個MKOverlayView的地圖視圖,這是通過委托方法完成的。

更新ParkMapViewController.swift中的委托方法:

func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
  if overlay is ParkMapOverlay {
    return ParkMapOverlayView(overlay: overlay, overlayImage: #imageLiteral(resourceName: "overlay_park"))
  } else if overlay is MKPolyline {
    let lineView = MKPolylineRenderer(overlay: overlay)
    lineView.strokeColor = UIColor.green
    return lineView
  } else if overlay is MKPolygon {
    let polygonView = MKPolygonRenderer(overlay: overlay)
    polygonView.strokeColor = UIColor.magenta
    return polygonView
  } else if let character = overlay as? Character {
    let circleView = MKCircleRenderer(overlay: character)
    circleView.strokeColor = character.color
    return circleView
  }
    
  return MKOverlayRenderer()
}

最后,更新loadSelectedOptions()以向用戶提供打開或關閉角色位置的選項:

case .mapCharacterLocation:
  addCharacterLocation()

您也可以刪除default:break語句,因為您已經涵蓋了所有可能的情況。

Build并運行應用程序,并打開角色覆蓋圖以查看每個人隱藏的位置!

有更先進的 - 也許更有效 - 的方法來創建疊加層。 一些替代方法是使用KML文件,MapBox tiles或其他第三方提供的資源。


源碼

1. Swift

首先看一下工程文件

然后看一下sb中的內容

下面就是詳細的代碼了。

1. Character.swift
import UIKit
import MapKit

class Character: MKCircle {
  
  var name: String?
  var color: UIColor?
  
  convenience init(filename: String, color: UIColor) {
    guard let points = Park.plist(filename) as? [String] else { self.init(); return }
    
    let cgPoints = points.map { CGPointFromString($0) }
    let coords = cgPoints.map { CLLocationCoordinate2DMake(CLLocationDegrees($0.x), CLLocationDegrees($0.y)) }
    
    let randomCenter = coords[Int(arc4random()%4)]
    let randomRadius = CLLocationDistance(max(5, Int(arc4random()%40)))
    
    self.init(center: randomCenter, radius: randomRadius)
    self.name = filename
    self.color = color
  }
}
2. Park.swift
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 {
    get {
      return CLLocationCoordinate2DMake(overlayBottomLeftCoordinate.latitude,
                                        overlayTopRightCoordinate.longitude)
    }
  }
  
  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))
    }
  }
  
  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)) }
  }
  
  static func plist(_ plist: String) -> Any? {
    guard let filePath = Bundle.main.path(forResource: plist, ofType: "plist"),
      let data = FileManager.default.contents(atPath: filePath) else { return nil }
    
    do {
      return try PropertyListSerialization.propertyList(from: data, options: [], format: nil)
    } catch {
      return nil
    }
  }
  
  static func parseCoord(dict: [String: Any], fieldName: String) -> CLLocationCoordinate2D{
    if let coord = dict[fieldName] as? String {
      let point = CGPointFromString(coord)
      return CLLocationCoordinate2DMake(CLLocationDegrees(point.x), CLLocationDegrees(point.y))
    }
    return CLLocationCoordinate2D()
  }
}
3. ParkMapOverlay.swift
import UIKit
import MapKit

class ParkMapOverlay: NSObject, MKOverlay {
  
  var coordinate: CLLocationCoordinate2D
  var boundingMapRect: MKMapRect
  
  init(park: Park) {
    boundingMapRect = park.overlayBoundingMapRect
    coordinate = park.midCoordinate
  }
}
4. ParkMapOverlayView.swift
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)
  }
}
5. AttractionAnnotation.swift
import UIKit
import MapKit

enum AttractionType: Int {
  case misc = 0
  case ride
  case food
  case firstAid
  
  func image() -> UIImage {
    switch self {
    case .misc:
      return #imageLiteral(resourceName: "star")
    case .ride:
      return #imageLiteral(resourceName: "ride")
    case .food:
      return #imageLiteral(resourceName: "food")
    case .firstAid:
      return #imageLiteral(resourceName: "firstaid")
    }
  }
}

class AttractionAnnotation: NSObject, MKAnnotation {
  var coordinate: CLLocationCoordinate2D
  var title: String?
  var subtitle: String?
  var type: AttractionType
  
  init(coordinate: CLLocationCoordinate2D, title: String, subtitle: String, type: AttractionType) {
    self.coordinate = coordinate
    self.title = title
    self.subtitle = subtitle
    self.type = type
  }
}
6. AttractionAnnotationView.swift
import UIKit
import MapKit

class AttractionAnnotationView: MKAnnotationView {
  
  // Required for MKAnnotationView
  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
  }
  
  override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
    super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
    guard let attractionAnnotation = self.annotation as? AttractionAnnotation else { return }
    
    image = attractionAnnotation.type.image()
  }
}
7. MapOptionsViewController.swift
import UIKit

enum MapOptionsType: Int {
  case mapBoundary = 0
  case mapOverlay
  case mapPins
  case mapCharacterLocation
  case mapRoute
  
  func displayName() -> String {
    switch (self) {
    case .mapBoundary:
      return "Park Boundary"
    case .mapOverlay:
      return "Map Overlay"
    case .mapPins:
      return "Attraction Pins"
    case .mapCharacterLocation:
      return "Character Location"
    case .mapRoute:
      return "Route"
    }
  }
}

class MapOptionsViewController: UIViewController {
  
  var selectedOptions = [MapOptionsType]()
}

// MARK: - UITableViewDataSource
extension MapOptionsViewController: UITableViewDataSource {
  
  func numberOfSections(in tableView: UITableView) -> Int {
    return 1
  }
  
  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 5
  }
  
  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "OptionCell")!
    
    if let mapOptionsType = MapOptionsType(rawValue: indexPath.row) {
      cell.textLabel!.text = mapOptionsType.displayName()
      cell.accessoryType = selectedOptions.contains(mapOptionsType) ? .checkmark : .none
    }
    
    return cell
  }
}

// MARK: - UITableViewDelegate
extension MapOptionsViewController: UITableViewDelegate {
  
  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    guard let cell = tableView.cellForRow(at: indexPath) else { return }
    guard let mapOptionsType = MapOptionsType(rawValue: indexPath.row) else { return }
    
    if (cell.accessoryType == .checkmark) {
      // Remove option
      selectedOptions = selectedOptions.filter { $0 != mapOptionsType}
      cell.accessoryType = .none
    } else {
      // Add option
      selectedOptions += [mapOptionsType]
      cell.accessoryType = .checkmark
    }
    
    tableView.deselectRow(at: indexPath, animated: true)
  }
}
8. ParkMapViewController.swift
import UIKit
import MapKit

class ParkMapViewController: UIViewController {
  
  @IBOutlet weak var mapView: MKMapView!
  
  var park = Park(filename: "MagicMountain")
  var selectedOptions : [MapOptionsType] = []
  
  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
  }
  
  // MARK: - Add methods
  func addOverlay() {
    let overlay = ParkMapOverlay(park: park)
    mapView.add(overlay)
  }
  
  func addAttractionPins() {
    guard let attractions = Park.plist("MagicMountainAttractions") as? [[String : String]] else { return }
    
    for attraction in attractions {
      let coordinate = Park.parseCoord(dict: attraction, fieldName: "location")
      let title = attraction["name"] ?? ""
      let typeRawValue = Int(attraction["type"] ?? "0") ?? 0
      let type = AttractionType(rawValue: typeRawValue) ?? .misc
      let subtitle = attraction["subtitle"] ?? ""
      let annotation = AttractionAnnotation(coordinate: coordinate, title: title, subtitle: subtitle, type: type)
      mapView.addAnnotation(annotation)
    }
  }
  
  func addRoute() {
    guard let points = Park.plist("EntranceToGoliathRoute") as? [String] else { return }
    
    let cgPoints = points.map { CGPointFromString($0) }
    let coords = cgPoints.map { CLLocationCoordinate2DMake(CLLocationDegrees($0.x), CLLocationDegrees($0.y)) }
    let myPolyline = MKPolyline(coordinates: coords, count: coords.count)
    
    mapView.add(myPolyline)
  }
  
  func addBoundary() {
    mapView.add(MKPolygon(coordinates: park.boundary, count: park.boundary.count))
  }
  
  func addCharacterLocation() {
    mapView.add(Character(filename: "BatmanLocations", color: .blue))
    mapView.add(Character(filename: "TazLocations", color: .orange))
    mapView.add(Character(filename: "TweetyBirdLocations", color: .yellow))
  }
  
  // MARK: Helper methods
  func loadSelectedOptions() {
    mapView.removeAnnotations(mapView.annotations)
    mapView.removeOverlays(mapView.overlays)
    
    for option in selectedOptions {
      switch (option) {
      case .mapOverlay:
        self.addOverlay()
      case .mapPins:
        self.addAttractionPins()
      case .mapRoute:
        self.addRoute()
      case .mapBoundary:
        self.addBoundary()
      case .mapCharacterLocation:
        self.addCharacterLocation()
      }
    }
  }
  
  override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    (segue.destination as? MapOptionsViewController)?.selectedOptions = selectedOptions
  }
  
  @IBAction func closeOptions(_ exitSegue: UIStoryboardSegue) {
    guard let vc = exitSegue.source as? MapOptionsViewController else { return }
    selectedOptions = vc.selectedOptions
    loadSelectedOptions()
  }
  
  @IBAction func mapTypeChanged(_ sender: UISegmentedControl) {
    mapView.mapType = MKMapType.init(rawValue: UInt(sender.selectedSegmentIndex)) ?? .standard
  }
}

// MARK: - MKMapViewDelegate
extension ParkMapViewController: MKMapViewDelegate {
  
  func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
    if overlay is ParkMapOverlay {
      return ParkMapOverlayView(overlay: overlay, overlayImage: #imageLiteral(resourceName: "overlay_park"))
    } else if overlay is MKPolyline {
      let lineView = MKPolylineRenderer(overlay: overlay)
      lineView.strokeColor = UIColor.green
      return lineView
    } else if overlay is MKPolygon {
      let polygonView = MKPolygonRenderer(overlay: overlay)
      polygonView.strokeColor = UIColor.magenta
      return polygonView
    } else if let character = overlay as? Character {
      let circleView = MKCircleRenderer(overlay: character)
      circleView.strokeColor = character.color
      return circleView
    }
    
    return MKOverlayRenderer()
  }
  
  func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
    let annotationView = AttractionAnnotationView(annotation: annotation, reuseIdentifier: "Attraction")
    annotationView.canShowCallout = true
    return annotationView
  }
}

下面是最終的所有選項都勾選的效果圖

下面是gif圖

后記

本篇主要講述了一個疊加視圖相關的簡單示例,感興趣給個贊或者關注~~~

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容