NEW: Join my free 100 Days of SwiftUI challenge today! >>

Integrating MapKit with SwiftUI

Paul Hudson    @twostraws   

Maps have been a core feature of iPhone since the very first device shipped way back in 2007, and the underlying framework has been available to developers for almost as long. It’s called MapKit, and like UIKit it’s available for us to use in SwiftUI if we don’t mind putting in some additional work – and yes, that does mean coordinators.

Let’s start off with something simple and work our way up. Make a new SwiftUI view called “MapView”, then add an import for MapKit at the top. This time we’re not going to use the protocol UIViewControllerRepresentable, because MapKit doesn’t use view controllers.

A classic way of building software is called “MVC”, which splits our code into three types of object Model (our data), View (our layouts), and Controller (glue code that connects Model and View). Apple uses MVC in UIKit and its other frameworks, including MapKit, but adds a fun twist: view controllers. Are they views, controllers, both, or neither? Apple doesn’t really answer that for us, which is why you’ll see a hundred variations of MVC in iOS app development.

When I teach UIKit, I start by explaining to folks that a view is one piece of layout, such as some text, a button, or an image, and a view controller is one screen of content. As you progress through your UIKit knowledge you learn that really you can have many view controllers on one screen, but it’s a helpful mental model while you’re learning.

All this matters because when we used UIImagePickerController it was designed to work as one full screen of information – we didn’t try to add functionality to it, because it was designed to work as one self-contained unit. In contrast, MapKit gives us MKMapView, and as you can tell from the name this is a view not a controller, which means it just shows content we provide to it.

This is why we don’t use UIViewControllerRepresentable when working with MapKit: MKMapView uses a view, and so we need to use UIViewRepresentable instead. Helpfully this works almost identically: we need to write methods called makeUIView() and updateUIView(), that handle instantiating a map view and updating it as our SwiftUI state changes. However, that update method is much more important for views than view controllers, because there’s a lot more cross-talk between SwiftUI code and UIView objects – whereas we left that method empty for view controllers, you’ll be using it a lot for views.

We’ll come back to updating soon, but for now we’ll use another empty method. As for the make method, this will make a new MKMapView and send it back – we’ll be adding more to this soon, but you’ve had enough chat now and I’m sure you’re keen to get moving!

Replace your current MapView struct with this:

struct MapView: UIViewRepresentable {
    func makeUIView(context: UIViewRepresentableContext<MapView>) -> MKMapView {
        let mapView = MKMapView()
        return mapView
    }

    func updateUIView(_ view: MKMapView, context: UIViewRepresentableContext<MapView>) {
    }
}

Before we move on, I want to show you a teensy bit of Swift magic. Back in project 13 when I introduced you to the UIViewControllerRepresentable protocol, we used typealias just briefly. This is Swift’s way of letting us create new names for existing types, which is usually done to make them easier to remember.

Well, both UIViewControllerRepresentable and UIViewRepresentable both include type aliases built into them. If you right-click on UIViewRepresentable and choose Jump To Definition you’ll see the generated interface for SwiftUI, and it should also show you this line inside the UIViewRepresentable protocol:

typealias Context = UIViewRepresentableContext<Self>

That creates a new typealias – a type name – called “Context”, and wherever Swift sees Context in code it will consider it the same as UIViewRepresentableContext<Self>, where Self is whatever type we’re working with. In practical terms, this means we can just write Context rather than UIViewRepresentableContext<MapView>, and they mean exactly the same thing.

Anyway, we’ve built the first version of our map view, so we can go ahead and use it. Go back to ContentView.swift and replace the text view with this:

MapView()
    .edgesIgnoringSafeArea(.all)

Xcode doesn’t do a great job of previewing map views right now, so I suggest you run the app in the simulator to see how it looks. You should find you can tap and drag the map around, but if you hold down the Option key you’ll see a second virtual finger appear so you and pinch and rotate freely. Not bad for only a handful of lines of code!

Of course, what you really want to do is bring the map to life with some place marks, so we’ll tackle that next…

LEARN SWIFTUI FOR FREE I have a massive, free SwiftUI video collection on YouTube teaching you how to build complete apps with SwiftUI – check it out!

BUY OUR BOOKS
Buy Pro Swift Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift (Vapor Edition) Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Server-Side Swift (Kitura Edition) Buy Beyond Code

Was this page useful? Let us know!

Average rating: 5.0/5