Git Product home page Git Product logo

map's Introduction

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.

map's People

Contributors

pauljohanneskraft avatar bucekjiri avatar fabiomsousa avatar

Stargazers

Mark Powell avatar

Watchers

James Cloos avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.