MKMapView wrapper for SwiftUI as drop-in to MapKit's SwiftUI view

Last update: Jun 22, 2022

Map

MapKit's SwiftUI implementation of Map (UIKit: MKMapView) is very limited. This library can be used as a drop-in solution (i.e. it features a very similar, but more powerful and customizable interface) to the existing Map and gives you so much more features and control:

🚀 Features

📍 Annotations

  • Create annotations from annotationItems as in the default MapKit SwiftUI implementation.
  • Or: Create annotations from a list of MKAnnotation objects - you can even use your existing MKAnnotationView implementations!

🖼 Overlays

  • Use a SwiftUI-style API based on Identifiable with overlay items and a closure to create overlays from these items
  • Or: Use existing MKOverlay / MKOverlayRenderer objects

🛠 Appearance / Behavior Customization

👀 Adapt visibility of:

  • Buildings
  • Compass
  • Pitch control
  • Scale
  • Traffic
  • User heading
  • User location
  • Zoom controls

🪄 Custom controls

💻 Supported Platforms

📱 iOS 13+
🖥 macOS 10.15+
📺 tvOS 13+
⌚️ watchOS 6+

Keep in mind that not all features are equally available on all platforms (based on what MapKit provides) and therefore might not be available here either. However, if you can use them using UIKit, there is a very high change that it is available here as well - if not: Let me/us know by creating an issue!

🧑🏽‍💻 Usage on iOS, macOS and tvOS

Very similar to MapKit's SwiftUI wrapper, you simply create a Map view inside the body of your view. You can define a region or mapRect, the map type (MKMapType), a pointOfInterestFilter (MKPointOfInterestFilter), interactions Modes (with values: .none, .pitch, .pan, .zoon, .rotate and .all - which can be combined as you wish) and showsUserLocation.

import Map
import SwiftUI

struct MyMapView: View {

    let locations: [MyLocation]
    let directions: MKDirections.Response
    
    @State private var region = MKCoordinateRegion()
    @State private var userTrackingMode = UserTrackingMode.follow

    var body: some View {
        Map(
          coordinateRegion: $region,
          type: .satelliteFlyover,
          pointOfInterestFilter: .excludingAll,
          informationVisibility: .default.union(.userLocation),
          interactionModes: [.pan, .rotate],
          userTrackingMode: $userTrackingMode,
          annotationItems: locations,
          annotationContent: { location in
              ViewMapAnnotation(coordinate: location.coordinate) {
                  Color.red
                    .frame(width: 24, height: 24)
                    .clipShape(Circle())
              }
          },
          overlays: directions.routes.map { $0.polyline },
          overlayContent: { overlay in
              RendererMapOverlay(overlay: overlay) { _, overlay in
                  if let polyline = overlay as? MKPolyline else {
                      let isFirstRoute = overlay === directions.routes.first?.overlay
                      let renderer = MKPolylineRenderer(polyline: polyline)
                      renderer.lineWidth = 6
                      renderer.strokeColor = isFirstRoute ? .systemBlue : .systemGray
                      return renderer
                  } else {
                      assertionFailure("Unknown overlay type found.")
                      return MKOverlayRenderer(overlay: overlay)
                  }
              }
          }
        )
        .onAppear {
            region = // ...
        }
    }

}

📍 Annotations: The modern approach

You can use a collection of items conforming to Identifiable and a closure that maps an item to its visual representation (available types: MapPin, MapMarker and ViewMapAnnotation for custom annotations from any SwiftUI View).

Map(
    coordinateRegion: $region,
    annotationItems: items,
    annotationContent: { item in
        if <first condition> {
            ViewMapAnnotation(coordinate: location.coordinate) {
                Color.red
                    .frame(width: 24, height: 24)
                    .clipShape(Circle())
             }
         else if <second condition> {
             MapMarker(coordinate: item.coordinate, tint: .red) // tint is `UIColor`, `NSColor` or `Color`
         } else {
             MapPin(coordinate: item.coordinate, tint: .blue) // tint is `UIColor`, `NSColor` or `Color`
         }
     }
)

📌 Annotations: The old-fashioned approach

Moving an existing code base over to SwiftUI is hard, especially when you want to keep methods, types and properties that you have previously built. This library, therefore, allows the use of MKAnnotation instead of being forced to the new Identifiable style. In the additional closure, you can use one of the options mentioned in the modern-approach. Alternatively, we also have an option to use your own MKAnnotationView implementations. Simply create a struct conforming to the following protocol and you are good to go.

public protocol MapAnnotation {

    static func registerView(on mapView: MKMapView)
    
    var annotation: MKAnnotation { get }

    func view(for mapView: MKMapView) -> MKAnnotationView?
    
}

In registerView(on:), your custom annotation implementation can register a cell type for dequeuing using MKMapView.register(_:forAnnotationViewWithReuseIdentifier:). To dequeue the registered cell, implement the view(for:) method, similar to MKMapViewDelegate.mapView(_:viewFor:).

Note: Please make sure not to create the value of the property annotation dynamically. You can either use an existing object or create the object in your type's initializer. Simply put: Do not make annotation a computed property!

🌃 Overlays: The modern approach

Similarly to how annotations are handled, you can also use a collection of Identifiable and a closure mapping it to specific overlay types. These overlay types currently contain MapCircle, MapMultiPolygon, MapMultiPolyline, MapPolygon and MapPolyline and this list can easily be extended by creating a type conforming to the following protocol:

public protocol MapOverlay {

    var overlay: MKOverlay { get }
    
    func renderer(for mapView: MKMapView) -> MKOverlayRenderer
    
}

In your implementation, the renderer(for:) method creates a renderer for the overlay, similar to MKMapViewDelegate.mapView(_:rendererFor:).

Note: Please make sure not to create the value of the property overlay dynamically. You can either use an existing object or create the object in your type's initializer. Simply put: Do not make overlay a computed property!

🖼 Overlays: The old-fashioned approach

Especially when working with MKDirections or when more customization to the MKOverlayRenderer is necessary, you can also provide an array of MKOverlay objects and use your own MKOverlayRenderer.

For this, we provide RendererMapOverlay:

Map(
    coordinateRegion: $region,
    overlays: directions.routes.map { $0.polyline },
    overlayContent: { overlay in
        RendererMapOverlay(overlay: overlay) { mapView, overlay in
            guard let polyline = overlay as? MKPolyline else {
                assertionFailure("Unknown overlay type encountered.")
                return MKMapOverlayRenderer(overlay: overlay)
            }
            let renderer = MKPolylineRenderer(polyline: polyline)
            renderer.lineWidth = 4
            renderer.strokeColor = .red
            return renderer
        }
    }
)

🪄 Custom Map Controls

For the use of MapCompass, MapPitchControl, MapScale and MapZoomControl you will need to associate both the Map and the control with some form of a shared key. This key needs to conform to the Hashable protocol. For each key, there must only be one Map (or MKMapView respectively) in the view hierarchy at once.

Example: We want to display a scale overlay at the topLeading edge of a Map. To accomplish this, let's take a look at the following code snippet.

struct MyMapView: View {

    @Binding var region: MKCoordinateRegion
    
    var body: some View {
        Map(coordinateRegion: $region)
            .mapKey(1)
            .overlay(alignment: .topLeading) {
                MapScale(key: 1, alignment: .leading, visibility: .visible)
                    .fixedSize()
                    .padding(12)
            }
    }
}

⌚️ Usage on watchOS

Since MapKit is very limited on watchOS, there is a separate (also similary limited) wrapper in this library. If you are only targeting watchOS, it might not make sense to use this library as the underlying feature set is already very limited (e.g. no overlay support, only a few kinds of possible annotations, etc).

We do include a drop-in interface though for projects that target multiple platforms and share code extensively across these platforms.

Map(
    coordinateRegion: $region,
    informationVisibility: [.userHeading, .userLocation],
    userTrackingMode: $userTrackingMode,
    annotationItems: annotationItems,
    annotationContent: { item in
        if <first condition> {
            ImageAnnotation(coordinate: item.coordinate, image: UIImage(...), centerOffset: CGPoint(x: 0, y: -2) 
        } else {
            MapPin(coordinate: item.coordinate, color: .red) // color can only be red, green or purple
        }
    }
)

🔩 Installation

Map is currently only available via Swift Package Manager. See this tutorial by Apple on how to add a package dependency to your Xcode project.

✍️ Author

Paul Kraft

📄 License

Map is available under the MIT license. See the LICENSE file for more info.

GitHub

https://github.com/pauljohanneskraft/Map
You might also like...

A drop-in UITableView/UICollectionView superclass category for showing empty datasets whenever the view has no content to display

A drop-in UITableView/UICollectionView superclass category for showing empty datasets whenever the view has no content to display

DZNEmptyDataSet Projects using this library Add your project to the list here and provide a (320px wide) render of the result. The Empty Data Set Patt

Jun 28, 2022

A drop-in mosaic collection view layout with a focus on simple customizations.

A drop-in mosaic collection view layout with a focus on simple customizations.

FMMosaicLayout is a mosiac collection view layout. There are a great number of media-based iOS applications that use UICollectionViewFlowLayout withou

Mar 20, 2022

A few drop-in SwiftUI components for easily importing and thumb-nailing files

FilesUI A few drop-in SwiftUI components for easily importing and thumb-nailing files Usage 1. Import Files To import files you can use the FileImport

Apr 15, 2022

RxSwift reactive wrapper for view gestures

RxSwift reactive wrapper for view gestures

RxGesture Usage To run the example project, clone the repo, in the Example folder open RxGesture.xcworkspace. You might need to run pod install from t

Jun 19, 2022

MessengerKit is a drop-in UI for messenger interfaces on iOS built in Swift

MessengerKit is a drop-in UI for messenger interfaces on iOS built in Swift

About MessengerKit is a drop-in UI for messenger interfaces on iOS built in Swift. Centred around a single UIViewController, MessengerKit is themeable

Jun 19, 2022

Throttle massive number of inputs in a single drop of one line API.

Throttle massive number of inputs in a single drop of one line API.

Icon credits: Lorc, Delapouite & contributors Throttler Throttler is a library that throttles unnecessarily repeated and massive inputs until the last

Jun 21, 2022

Codeless drop-in universal library allows to prevent issues of keyboard sliding up and cover UITextField/UITextView. Neither need to write any code nor any setup required and much more.

Codeless drop-in universal library allows to prevent issues of keyboard sliding up and cover UITextField/UITextView. Neither need to write any code nor any setup required and much more.

IQKeyboardManager While developing iOS apps, we often run into issues where the iPhone keyboard slides up and covers the UITextField/UITextView. IQKey

Jun 26, 2022

RichEditorView is a simple, modular, drop-in UIView subclass for Rich Text Editing.

RichEditorView is a simple, modular, drop-in UIView subclass for Rich Text Editing.

RichEditorView RichEditorView is a simple, modular, drop-in UIView subclass for Rich Text Editing. Written in Swift 4 Supports iOS 8+ through Cocoapod

Jun 24, 2022

A Material Design drop down for iOS

A Material Design drop down for iOS

A Material Design drop down for iOS written in Swift. Demo Do pod try DropDown in your console and run the project to try a demo. To install CocoaPods

Jun 21, 2022

A drop-in universal library helps you to manage the navigation bar styles and makes transition animations smooth between different navigation bar styles

A drop-in universal library helps you to manage the navigation bar styles and makes transition animations smooth between different navigation bar styles

A drop-in universal library helps you to manage the navigation bar styles and makes transition animations smooth between different navigation bar styles while pushing or popping a view controller for all orientations. And you don't need to write any line of code for it, it all happens automatically.

Jun 12, 2022

UILabel drop-in replacement supporting Hashtags

UILabel drop-in replacement supporting Hashtags

ActiveLabel.swift UILabel drop-in replacement supporting Hashtags (#), Mentions (@), URLs (http://), Emails and custom regex patterns, written in Swif

Jun 25, 2022

Convert text with HTML tags, links, hashtags, mentions into NSAttributedString. Make them clickable with UILabel drop-in replacement.

Convert text with HTML tags, links, hashtags, mentions into NSAttributedString. Make them clickable with UILabel drop-in replacement.

Atributika is an easy and painless way to build NSAttributedString. It is able to detect HTML-like tags, links, phone numbers, hashtags, any regex or

Jun 21, 2022

Drop in user input validation for your iOS apps.

Drop in user input validation for your iOS apps.

Validator Validator is a user input validation library written in Swift. It's comprehensive, designed for extension, and leaves the UI up to you. Here

Jun 6, 2022

This component allows for the transfer of data items between collection views through drag and drop

This component allows for the transfer of data items between collection views through drag and drop

Drag and Drop Collection Views Written for Swift 4.0, it is an implementation of Dragging and Dropping data across multiple UICollectionViews. Try it

Jun 11, 2022

Stock is a MacOS menu bar app that helps you quickly save a web link, a file link, or a text by using drag and drop

Stock is a MacOS menu bar app that helps you quickly save a web link, a file link, or a text by using drag and drop

Stock is a MacOS menu bar app that helps you quickly save a web link, a file link, or a text by using drag and drop

Apr 21, 2022

Convert text with HTML tags, links, hashtags, mentions into NSAttributedString. Make them clickable with UILabel drop-in replacement.

Convert text with HTML tags, links, hashtags, mentions into NSAttributedString. Make them clickable with UILabel drop-in replacement.

Atributika is an easy and painless way to build NSAttributedString. It is able to detect HTML-like tags, links, phone numbers, hashtags, any regex or

Jun 20, 2022

MacOS app which allows drag and drop of images to BLE thermal printers

MacOS app which allows drag and drop of images to BLE thermal printers

Print2BLE Copyright (c) 2021 BitBank Software, Inc. Written by Larry Bank [email protected] What is it? This project is a MacOS GUI applicatio

May 12, 2022

iOS drop-in library presenting a 2048-style game

iOS drop-in library presenting a 2048-style game

iOS-2048 iOS drop-in library presenting a clean-room Objective-C/Cocoa implementation of the game 2048. Screenshot Instructions The included sample ap

Jun 22, 2022

Convert text with HTML tags, links, hashtags, mentions into NSAttributedString. Make them clickable with UILabel drop-in replacement.

Convert text with HTML tags, links, hashtags, mentions into NSAttributedString. Make them clickable with UILabel drop-in replacement.

Convert text with HTML tags, links, hashtags, mentions into NSAttributedString. Make them clickable with UILabel drop-in replacement.

Jun 21, 2022
Comments
  • 1. I'm very interested in this project!

    I'm having trouble getting a demo app to compile and run. Would you be willing to create a very simple example app to get a new users for your package up and running?

    Reviewed by MatthewMerritt at 2022-05-01 18:34
  • 2. How to display a small window after clicking on annotation?

    Looks like a great project, thanks for your work! Is it possible to click on a view in ViewMapAnnotation to bring up a small window with more information? I tried putting a Button in it, or just a TapGesture and clicking on it would display a different view, but it didn't work.

    iOS 15 - SwiftUI - Xcode 13.4 - Swift Package Manager

    Reviewed by sandorbogyo at 2022-05-17 09:40
  • 3. Add DynamicRendererMapOverlay

    Still open questions:

    • How to enable animations?
    • How to allow for integration with AVFoundation, AVKit and so on?
    • How to make setting coordinate/boundingMapRect of the overlay dynamically possible?
    • How to allow for SwiftUI being used to draw the overlay instead of falling back to CoreGraphics?
    Reviewed by pauljohanneskraft at 2022-04-27 17:15
360° flyover on a MKMapView 🚁
360° flyover on a MKMapView 🚁

FlyoverKit enables you to present stunning 360° flyover views on an MKMapView with zero effort while maintaining full configuration possibilities. Fly

Jun 4, 2022
A Swift wrapper for forward and reverse geocoding of OpenStreetMap data

Nominatim NominatimKit is a Swift wrapper for forward and reverse geocoding of OpenStreetMap data. Why? Geocoding location data on iOS requires the us

Feb 5, 2022
MapKit, but for SwiftUI

MapKit, but for SwiftUI

Apr 5, 2022
Use any custom view as custom callout view for MKMapView with cool animations. Use any image as annotation view.
Use any custom view as custom callout view for MKMapView with cool animations. Use any image as annotation view.

MapViewPlus About MapViewPlus gives you the missing methods of MapKit which are: imageForAnnotation and calloutViewForAnnotationView delegate methods.

May 30, 2022
SwiftUI wrapper for MapKit's MKMapView (UIKit).
SwiftUI wrapper for MapKit's MKMapView (UIKit).

SwiftUIMKMapView SwiftUI wrapper for MapKit's MKMapView (UIKit). ▶️ Usage Add as a dependecy to your project using Swift Package Manager. Embed map vi

Apr 4, 2022
360° flyover on a MKMapView 🚁
360° flyover on a MKMapView 🚁

FlyoverKit enables you to present stunning 360° flyover views on an MKMapView with zero effort while maintaining full configuration possibilities. Fly

Jun 4, 2022
Request the Location Services via a 3D 360° flyover MKMapView 🗺
Request the Location Services via a 3D 360° flyover MKMapView 🗺

STLocationRequest STLocationRequest is a simple and elegant way to request the users location services at the very first time. The STLocationRequestCo

May 16, 2022
Drag and drop between your apps in split view mode on iOS 9
Drag and drop between your apps in split view mode on iOS 9

SplitViewDragAndDrop Easily add drag and drop to pass data between your apps Setup Add pod 'SplitViewDragAndDrop' to your Podfile or copy the "SplitVi

Jun 5, 2022
Drop in GIF Collection View. Uses Tenor as default GIFs provider.
Drop in GIF Collection View. Uses Tenor as default GIFs provider.

Drop in GIF Collection View. Uses Tenor as default GIFs provider. This will allow you to easily integrate GIF image search into your app or you can use this as a GIF keyboard for your messaging needs.

May 7, 2022
Objective-C library for drag-n-drop of UITableViewCells in a navigation hierarchy of view controllers.
Objective-C library for drag-n-drop of UITableViewCells in a navigation hierarchy of view controllers.

ios-dragable-table-cells Support for drag-n-drop of UITableViewCells in a navigation hierarchy of view controllers. You drag cells by tapping and hold

Mar 20, 2022