9

I am trying to learn MapKit and am now trying to add an image (not a polygon, circle, rectangle, etc.) as an overlay to the map view. I can't seem to find any sample code to help explain how to do this.

So far my code in ViewController.swift is:

import UIKit
import MapKit
import CoreLocation

class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {

    @IBOutlet var mapView: MKMapView!

    var locationManager: CLLocationManager!
    var mapOverlay: MKOverlay!

    override func viewDidLoad() {
        super.viewDidLoad()

        //Setup our Location Manager
        locationManager = CLLocationManager()
        locationManager.delegate = self
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.startUpdatingLocation()

        //Setup our Map View
        mapView.delegate = self
        mapView.mapType = MKMapType.Satellite
        mapView.showsUserLocation = true
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}

I know I need to use:

func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer

and the drawMapRect function but I don't know what to put in them. Also, it would be useful if all the code is in ViewController.swift

I only need one overlay so I don't need a class. I need it to be an image overlay (so it can resize), not an annotation. I also tried to follow the tutorial at http://www.raywenderlich.com/30001/overlay-images-and-overlay-views-with-mapkit-tutorial but there is not a section with all the code and I find it hard to follow along. Can you people please help me how to create image MKOverlays and add them to MKMapKit?

Thanks in advance...

2 Answers2

9

Swift 3

The following works for me in Swift 3.

class ImageOverlay : NSObject, MKOverlay {

    let image:UIImage
    let boundingMapRect: MKMapRect
    let coordinate:CLLocationCoordinate2D

    init(image: UIImage, rect: MKMapRect) {
        self.image = image
        self.boundingMapRect = rect
        self.coordinate = rect.centerCoordinate
    }
}

class ImageOverlayRenderer : MKOverlayRenderer {

    override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) {

        guard let overlay = self.overlay as? ImageOverlay else {
            return
        }

        let rect = self.rect(for: overlay.boundingMapRect) 

        UIGraphicsPushContext(context)
        overlay.image.draw(in: rect)
        UIGraphicsPopContext()
    }
}

Then as usual, in your MapView delegate:

func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {

    if overlay is ImageOverlay {
        return ImageOverlayRenderer(overlay: overlay)
    }
    ...
}

And whenever you need to add an image to your map:

let overlay = ImageOverlay(image: myImage, rect: desiredLocationAsMapRect)
mapView.add(overlay)

Note, I always ensure that the rect has the same aspect ratio as the image. Otherwise, I suspect image distortion will result.

biomiker
  • 3,108
  • 1
  • 29
  • 26
  • How can I ensure that the rect is sized appropriatelt for image------My code------ let heightInPoints = Image.size.height let heightInPixels = heightInPoints * Image.scale let widthInPoints = Image.size.width let widthInPixels = widthInPoints * Image.scale let mkMapSize = MKMapSize(width: Double(widthInPixels), height: Double(heightInPixels)) let mkMapPoint = MKMapPointForCoordinate(locationCoordinates) let mkMapRect = MKMapRect(origin: mkMapPoint, size: mkMapSize)----- But overlay is not apperaing – Abin Baby Apr 06 '18 at 13:45
  • I edited my answer to clarify the note regarding rect size, I really just meant that it works best if the rect's aspect ratio is the same as the image. It's hard for me to read the code in your comment, but it does look to me like you are using the image size directly as the map size which is not what you want. The map rect should be determined by the geographical coordinates on the map where you want the image to be rendered. – biomiker May 09 '18 at 20:59
  • @biomiker seems like this solution forces the image to be the same exact size as the map rect. How can we set image size? – grantespo Oct 29 '19 at 18:13
  • @grantespo I'm not sure I understand your question. The map rect will determine the size of the overlay on the map. The image will be rendered into the map rect and scaled appropriately. What is it that you want to change? – biomiker Nov 01 '19 at 04:45
1

Apple do have some sample code for displaying overlays but it is in Objective-C so you will have to transpose it to Swift.

Essentially there are two things you need to do (and both can be done in your view controller). The first is to create the overlay and add it to the map i.e. somewhere in viewDidLoad():

    var points = [CLLocationCoordinate2D(latitude: -29.8122, longitude: 148.6351),
                  CLLocationCoordinate2D(latitude: -27.9307, longitude: 148.6351),
                  CLLocationCoordinate2D(latitude: -27.9307, longitude: 150.9909),
                  CLLocationCoordinate2D(latitude: -29.8122, longitude: 150.9909)]
    let tile = MKPolygon(coordinates: &points, count: points.count)
    tile.title = "Moree"
    mapView.addOverlay(tile)

Note that points has to be a var as you are dereferencing it for the tile creation.

The second step is then to define the renderer:

// mapView delegate function
  func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {

    if overlay.isKindOfClass(MKPolygon) {
      let renderer = MKPolygonRenderer(overlay: overlay)

      renderer.fillColor = UIColor.cyanColor().colorWithAlphaComponent(0.2)
      renderer.strokeColor = UIColor.blueColor().colorWithAlphaComponent(0.7)
      renderer.lineWidth = 3
      return renderer
    }
    fatalError()
  }

Using Swift 2.1/Xcode 7.2, this renders a tile for me in a location in Australia. So you may want to tweak the lat/lons to give you an overlay in your map - map defaults are to your continent.

timbo
  • 13,244
  • 8
  • 51
  • 71