MapKit框架详细解析(十四) —— MapKit Overlay Views(二)

版本记录

版本号 时间
V1.0 2020.06.20 星期六

前言

MapKit框架直接从您的应用界面显示地图或卫星图像,调出兴趣点,并确定地图坐标的地标信息。接下来几篇我们就一起看一下这个框架。感兴趣的看下面几篇文章。
1. MapKit框架详细解析(一) —— 基本概览(一)
2. MapKit框架详细解析(二) —— 基本使用简单示例(一)
3. MapKit框架详细解析(三) —— 基本使用简单示例(二)
4. MapKit框架详细解析(四) —— 一个叠加视图相关的简单示例(一)
5. MapKit框架详细解析(五) —— 一个叠加视图相关的简单示例(二)
6. MapKit框架详细解析(六) —— 添加自定义图块(一)
7. MapKit框架详细解析(七) —— 添加自定义图块(二)
8. MapKit框架详细解析(八) —— 添加自定义图块(三)
9. MapKit框架详细解析(九) —— 地图特定区域放大和创建自定义地图annotations(一)
10. MapKit框架详细解析(十) —— 地图特定区域放大和创建自定义地图annotations(二)
11. MapKit框架详细解析(十一) —— 自定义MapKit Tiles(一)
12. MapKit框架详细解析(十二) —— 自定义MapKit Tiles(二)
13. MapKit框架详细解析(十三) —— MapKit Overlay Views(一)

源码

1. Swift

首先看下工程组织结构

MapKit框架详细解析(十四) —— MapKit Overlay Views(二)_第1张图片

接着看下sb中的内容

下面就是源码了

1. Park.swift
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 {
    return CLLocationCoordinate2D(
      latitude: overlayBottomLeftCoordinate.latitude,
      longitude: overlayTopRightCoordinate.longitude)
  }

  var overlayBoundingMapRect: MKMapRect {
    let topLeft = MKMapPoint(overlayTopLeftCoordinate)
    let topRight = MKMapPoint(overlayTopRightCoordinate)
    let bottomLeft = MKMapPoint(overlayBottomLeftCoordinate)

    return MKMapRect(
      x: topLeft.x,
      y: topLeft.y,
      width: fabs(topLeft.x - topRight.x),
      height: 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 { NSCoder.cgPoint(for: $0) }
    boundary = cgPoints.map { CLLocationCoordinate2D(
      latitude: CLLocationDegrees($0.x),
      longitude: 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 = NSCoder.cgPoint(for: coord)
      return CLLocationCoordinate2D(
        latitude: CLLocationDegrees(point.x),
        longitude: CLLocationDegrees(point.y))
    }
    return CLLocationCoordinate2D()
  }
}
2. Character.swift
import MapKit

// 1
class Character: MKCircle {
  // 2
  private var name: String?
  var color: UIColor?

  // 3
  convenience init(filename: String, color: UIColor) {
    guard let points = Park.plist(filename) as? [String] else {
      self.init()
      return
    }

    let cgPoints = points.map { NSCoder.cgPoint(for: $0) }
    let coords = cgPoints.map {
      CLLocationCoordinate2D(latitude: CLLocationDegrees($0.x), longitude: CLLocationDegrees($0.y))
    }

    let randomCenter = coords[Int.random(in: 0...3)]
    let randomRadius = CLLocationDistance(Int.random(in: 5...39))

    self.init(center: randomCenter, radius: randomRadius)
    self.name = filename
    self.color = color
  }
}
3. ParkMapOverlay.swift
import MapKit

class ParkMapOverlay: NSObject, MKOverlay {
  let coordinate: CLLocationCoordinate2D
  let boundingMapRect: MKMapRect

  init(park: Park) {
    boundingMapRect = park.overlayBoundingMapRect
    coordinate = park.midCoordinate
  }
}
4. ParkMapOverlayView.swift
import MapKit

class ParkMapOverlayView: MKOverlayRenderer {
  let overlayImage: UIImage

  // 1
  init(overlay: MKOverlay, overlayImage: UIImage) {
    self.overlayImage = overlayImage
    super.init(overlay: overlay)
  }

  // 2
  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 MapKit

// 1
enum AttractionType: Int {
  case misc = 0
  case ride
  case food
  case firstAid

  func image() -> UIImage {
    switch self {
    case .misc:
      return UIImage(imageLiteralResourceName: "star") // #imageLiteral(resourceName: "star")
    case .ride:
      return UIImage(imageLiteralResourceName: "ride") //#imageLiteral(resourceName: "ride")
    case .food:
      return UIImage(imageLiteralResourceName: "food") //#imageLiteral(resourceName: "food")
    case .firstAid:
      return UIImage(imageLiteralResourceName: "firstaid") //#imageLiteral(resourceName: "firstaid")
    }
  }
}

// 2
class AttractionAnnotation: NSObject, MKAnnotation {
  // 3
  let coordinate: CLLocationCoordinate2D
  let title: String?
  let subtitle: String?
  let type: AttractionType

  // 4
  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 MapKit

class AttractionAnnotationView: MKAnnotationView {
  // 1
  // Required for MKAnnotationView
  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
  }

  // 2
  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. ContentView.swift
import SwiftUI
import MapKit

let park = Park(filename: "MagicMountain")
let mapView = MKMapView(frame: UIScreen.main.bounds)

struct MapView: UIViewRepresentable {
  func makeUIView(context: Context) -> MKMapView {
    let latDelta = park.overlayTopLeftCoordinate.latitude - park.overlayBottomRightCoordinate.latitude

    // Think of a span as a tv size, measure from one corner to another
    let span = MKCoordinateSpan(latitudeDelta: fabs(latDelta), longitudeDelta: 0.0)
    let region = MKCoordinateRegion(center: park.midCoordinate, span: span)

    mapView.region = region
    mapView.delegate = context.coordinator

    return mapView
  }

  func updateUIView(_ uiView: MKMapView, context: Context) {
  }

  // Acts as the MapView delegate
  class Coordinator: NSObject, MKMapViewDelegate {
    var parent: MapView

    init(_ parent: MapView) {
      self.parent = parent
    }

    func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
      if overlay is ParkMapOverlay {
        return ParkMapOverlayView(
          overlay: overlay,
          overlayImage: UIImage(imageLiteralResourceName: "overlay_park"))
      } else if overlay is MKPolyline {
        let lineView = MKPolylineRenderer(overlay: overlay)
        lineView.strokeColor = .green
        return lineView
      } else if overlay is MKPolygon {
        let polygonView = MKPolygonRenderer(overlay: overlay)
        polygonView.strokeColor = .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
    }
  }

  func makeCoordinator() -> Coordinator {
    Coordinator(self)
  }
}

struct ContentView: View {
  @State var mapBoundary = false
  @State var mapOverlay = false
  @State var mapPins = false
  @State var mapCharacterLocation = false
  @State var mapRoute = false

  var body: some View {
    NavigationView {
      MapView()
        .navigationBarTitle("", displayMode: .inline)
        .navigationBarItems(leading:
          HStack {
            Button(":Bound:") {
              self.mapBoundary.toggle()
              self.updateMapOverlayViews()
            }
            .foregroundColor(mapBoundary ? .white : .red)
            .background(mapBoundary ? Color.green : Color.clear)

            Button(":Overlay:") {
              self.mapOverlay.toggle()
              self.updateMapOverlayViews()
            }
            .foregroundColor(mapOverlay ? .white : .red)
            .background(mapOverlay ? Color.green : Color.clear)

            Button(":Pins:") {
              self.mapPins.toggle()
              self.updateMapOverlayViews()
            }
            .foregroundColor(mapPins ? .white : .red)
            .background(mapPins ? Color.green : Color.clear)

            Button(":Characters:") {
              self.mapCharacterLocation.toggle()
              self.updateMapOverlayViews()
            }
            .foregroundColor(mapCharacterLocation ? .white : .red)
            .background(mapCharacterLocation ? Color.green : Color.clear)

            Button(":Route:") {
              self.mapRoute.toggle()
              self.updateMapOverlayViews()
            }
            .foregroundColor(mapRoute ? .white : .red)
            .background(mapRoute ? Color.green : Color.clear)
          }
        )
    }
  }

  func addOverlay() {
    let overlay = ParkMapOverlay(park: park)
    mapView.addOverlay(overlay)
  }

  func addAttractionPins() {
    // 1
    guard let attractions = Park.plist("MagicMountainAttractions") as? [[String: String]] else { return }

    // 2
    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"] ?? ""
      // 3
      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 { NSCoder.cgPoint(for: $0) }
    let coords = cgPoints.map { CLLocationCoordinate2D(
      latitude: CLLocationDegrees($0.x),
      longitude: CLLocationDegrees($0.y))
    }
    let myPolyline = MKPolyline(coordinates: coords, count: coords.count)

    mapView.addOverlay(myPolyline)
  }

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

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

  func updateMapOverlayViews() {
    mapView.removeAnnotations(mapView.annotations)
    mapView.removeOverlays(mapView.overlays)

    if mapBoundary { addBoundary() }
    if mapOverlay { addOverlay() }
    if mapPins { addAttractionPins() }
    if mapCharacterLocation { addCharacterLocation() }
    if mapRoute { addRoute() }
  }
}

后记

本篇主要讲述了Overlay Views,感兴趣的给个赞或者关注~~~

MapKit框架详细解析(十四) —— MapKit Overlay Views(二)_第2张图片

你可能感兴趣的:(MapKit框架详细解析(十四) —— MapKit Overlay Views(二))