Integrating MapKit in SwiftUI for Interactive Maps

Embarking on the journey of integrating MapKit in SwiftUI opens up a world of possibilities for creating interactive and dynamic maps in your iOS applications. This powerful combination allows developers to leverage Apple’s robust MapKit API alongside SwiftUI’s modern UI framework, ensuring a seamless and efficient development experience.

Navigating through the intricacies of these technologies can be daunting, especially without a clear guide. Whether you’re on a tight deadline or just starting to dip your toes into the world of iOS development, this article is tailored to make your journey smoother and more straightforward.

We aim to demystify the process of creating a map with MapKit in SwiftUI, providing you with a comprehensive guide that covers everything from the basics of SwiftUI to the advanced functionalities of MapKit. By the end of this article, you’ll have a clear understanding and a solid foundation, ready to implement interactive maps into your SwiftUI applications with confidence.

NOTE: If you don’t have a background in Swift development, you might not be able to follow the article. However, you can find more articles introducing you to Swift here.

Introducing SwiftUI

SwiftUI represents the transition to a more modern and intuitive UI design paradigm in the Apple ecosystem. Apple designed SwiftUI expressly to work and enhance its Swift language infrastructure. With it, Apple allowed developers to build app user interfaces using code.

Apple states in its documentation that “SwiftUI provides views, controls, and layout structures for declaring your app’s user interface. The framework provides event handlers for delivering taps, gestures, and other types of input to your app, and tools to manage the flow of data from your app’s models down to the views and controls that users see and interact with.”

A typical SwiftUI fresh project contains two files. A ContentView.swift file and an <APP_NAME>App.swift file. In this case, APP_NAME stands for the name provided for the project.

Developers construct SwiftUI views in regular Swift classes. These classes are referred to as View Classes and follow a basic structure. The View struct specifies the design of the view itself and its functionality. Meanwhile, a PreviewView struct works as a helper for the convenient emulator displaying the view in real-time.

Furthermore, a single variable of type View called “body” defines the body of the ContentView. Any change to this variable will result in an observable modification to the current view.

When you create a new project, the base view class will contain a simple TextView element with the text “Hello World!”.

If you want to learn more about the structure and logic constituting SwiftUI, I suggest you check out these other articles.

What is MapKit?

Alright, so what is MapKit? And how do we use it to create a map in SwiftUI?

Put simply, MapKit is one of Apple’s many APIs to access and customize core features within its iOS ecosystem. One such core feature is the interactive map view that apple uses in its Maps app, which is accessible to all developers with this API.

If you are not clear on what I mean by API, in this case, it is the set of libraries and objects that we can import into our project to make use of more sophisticated and, sometimes restricted, features within the OS. Think of it like the toolsets and guidelines that Apple allows you to use to build your own version of a feature without worrying so much about the complexity behind the curtains.

As apple states, MapKit can be used to:

  • Embed maps directly into your app’s windows and views.
  • Add annotations and overlays to a map to call out points of interest.
  • Add LookAround capabilities to enable users to explore locations at street level.
  • Respond to user interactions with well-known points of interest, geographical features, and boundaries.
  • Provide text completion to make it easy for users to search for a destination or point of interest.

The Maps app and its corresponding API have been a core feature of the iPhone since Apple launched the device in 2007. Since then, the approach to implementing a map view on an app has changed significantly. However, it is now easier than ever to create an elegant and responsive map view with SwiftUI.

Let’s get to it.

Integrating MapKit in SwiftUI

Surprisingly, implementing a simple map view on an app with MapKit is almost trivial.

First, you need to import the MapKit library into your Swift class. Then you need to create a @State variable that will represent the region data for the map view to display. In this case, we can use the MKCoordinateRegion object to build a representable entity that the map view can use.

Finally, you just need to add the Map element into your body and pass the region state variable as the coordinateRegion to display.

import SwiftUI
import MapKit

struct ContentView: View {
    @State private var region =
        MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 37.4300,
                                                          longitude: -122.1700),
                           span: MKCoordinateSpan(latitudeDelta: 0.02,
                                                  longitudeDelta: 0.02)
                    )

    var body: some View {
        VStack {
            Map(coordinateRegion: $region)
                .edgesIgnoringSafeArea(.all)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

And that’s it, really. Not intimidating at all, is it?

Integrating MapKit in SwiftUI

Now, this is just the groundwork to have a fully featured map view for your users. You need to do a bit more work to enable features like location detection and markers.

Customizing a Map In SwiftUI

In order to detect the user’s location, we need to make use of another one of Apple’s APIs. CoreLocation.

Add the CoreLocation import to the class and remove the previously added state variables and map view. For this implementation, we will create a custom MapView struct representation with the help of the UIViewRepresentable protocol.

Now, create the MapView struct below the ContentView and add the following code.

struct MapView: UIViewRepresentable {
    // Location Manager object to request location updates from the core API
    var locationManager = CLLocationManager()

    // Setup function for the representable
    func setup() {
        // Set the location manager properties
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.requestWhenInUseAuthorization()
        locationManager.requestAlwaysAuthorization()
    }

    // Function to configure the representable view
    func makeUIView(context: Context) -> MKMapView {
        // Call the setup function
        setup()

        // Initialize the representable view
        let mapView = MKMapView(frame: UIScreen.main.bounds)
        mapView.showsUserLocation = true
        mapView.userTrackingMode = .follow

        // Return the representable view
        return mapView
    }

    // Function to update the representable view
    func updateUIView(_ uiView: MKMapView, context: Context) {
      // Do nothing
    }
}

As you can see, we first initialize the location manager, which will handle the request for location updates to the device’s internal GPS and any other mechanism for locating the user. This is the tool the CoreLocation provides developers to manage request frequency and reduce the impact on battery and other possible effects.

Then, the makeUIView() and updateUIView() functions allow us to define and update the representable view that SwiftUI will use. In this case, we create an MKMapView instance, set it up, and return it. Additionally, the location manager is set up in a function on app initialization.

Now, you can use this representable in the body of the ContentView like so.

import SwiftUI
import MapKit
import CoreLocation

struct ContentView: View {
    var body: some View {
        VStack {
            MapView()
        }
    }
}

MapKit Permissions

Before you run this code, it is essential to add the proper permission requirements in the application info section. To do this, go to the application configuration section and add the following two entries.

MapKit in SwiftUI permissions

This will ensure that the app requests the user for permission to track them by the location manager. Otherwise, location requests would be ignored.

Now, proceed to run the app.

Integrating MapKit in SwiftUI

Excellent.

How can I add MapKit Annotations?

How about adding annotations to the map?

Well, that’s quite simple.

First, create a function that will serve as the provider of annotations. In a real-world application, that could be a database or a remote service. Next, add an annotations property on the MapView struct and define it as an array of MKPointAnnotation. Then, add the annotations to the mapView instance using the addAnnotations() setter. Make sure to check that the annotations are there. Finally, pass the annotations to the MapView on the body.

import SwiftUI
import MapKit
import CoreLocation

struct ContentView: View {
    var body: some View {
        VStack {
            MapView(annotations: getAnnotations())
        }
    }
}

// Function that retrieves annotations from a service or DB
func getAnnotations() -> [MKPointAnnotation] {
    // Here we are hardcoding the annotations for illustration purposes only
    let annotation1 = MKPointAnnotation()
    annotation1.title = "Restaurant"
    annotation1.coordinate = CLLocationCoordinate2D(latitude: 40.7128,
                                                    longitude: 74.0060)

    let annotation2 = MKPointAnnotation()
    annotation2.title = "Hospital"
    annotation2.coordinate = CLLocationCoordinate2D(latitude: 47.6062,
                                                    longitude: 122.3321)

    return [annotation1, annotation2]
}

struct MapView: UIViewRepresentable {
    // Location Manager object to request location updates from the core API
    var locationManager = CLLocationManager()

    // Annotations for the map
    var annotations: [MKPointAnnotation]?

    // Setup function for the representable
    func setup() {
        // Set the location manager properties
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.requestWhenInUseAuthorization()
        locationManager.requestAlwaysAuthorization()
    }

    // Function to configure the representable view
    func makeUIView(context: Context) -> MKMapView {

        // Call the setup function
        setup()

        // Initialize the representable view
        let mapView = MKMapView(frame: UIScreen.main.bounds)
        mapView.showsUserLocation = true
        mapView.userTrackingMode = .follow

        // Provide annotations if they exists
        if let annotations = annotations {
            mapView.addAnnotations(annotations)
        }

        // Return the representable view
        return mapView
    }

    // Function to update the representable view
    func updateUIView(_ uiView: MKMapView, context: Context) {
      // Do nothing
    }
}

Now, run your code and check the results. Remember that the annotations in this example might not be near you, so they might not show up immediately until you move to them.

You can find the complete code of this example here.

Conclusion

Working with a modern and relatively young technology like SwiftUI can be challenging and stressful. When you don’t have the expertise and extensive experience necessary to work your way through hurdles in a platform that is too new to have good community backlogs and documentation, it can feel like venturing into a dense forest at night where you know for a fact hostile creatures are lurking. That is not a very fun place to start a learning journey, let alone as a fresh developer.

That is why it is imperative to have resources like this where experienced developers can share their findings in their journeys to the unknown.

If you want to learn more about SwiftUI and security I encourage you to check the other posts I have here.

This post was originally written for and published by Waldo.com


Posted

in

,

by

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.