TEAM LICENSES: Save money and learn new skills through a Hacking with Swift+ team license >>

Adding user locations to a map

Paul Hudson    @twostraws   

This project is going to be based around a map view, asking users to add places to the map that they want to visit. To do that we need to place a Map so that it takes up our whole view, track its annotations, and also whether or not the user is viewing place details.

We’re going to start with a full-screen Map view, giving it an initial position showing the UK – you're welcome to change that, of course!

First, add an extra import line so we get access to MapKit’s data types:

import MapKit

Second, add a property inside ContentView that will store the start position for the map:

let startPosition = MapCameraPosition.region(
    MKCoordinateRegion(
        center: CLLocationCoordinate2D(latitude: 56, longitude: -3),
        span: MKCoordinateSpan(latitudeDelta: 10, longitudeDelta: 10)
    )
)

And now we can fill in the body property

Map(initialPosition: startPosition)

If you run the app now you’ll see you can move the map around freely – if you want to change the map style, now is a good time to try using .mapStyle(.hybrid) or similar, for example.

All this work by itself isn’t terribly interesting, so the next step is to let users tap on the map to add place marks. Previously we used buttons for handling screen taps, but in this instance we need something different called a tap gesture – a new modifier we can add to any view to trigger code when it's tapped by the user.

Important: Many SwiftUI developers over-use tap gestures, and it causes all sorts of problems for users who rely on screen readers. If possible, it's always a better idea to use a button or other built-in control rather than adding a tap gesture. In this case we have no choice but to use a tap gesture, because it tells us where on the map the user tapped.

Go ahead and modify the Map to this:

Map(initialPosition: startPosition)
    .onTapGesture { position in
        print("Tapped at \(position)")
    }

If you run the app again you'll see how the tap gesture doesn't interfere with the default map gestures – you can still pan around, pinch to zoom, and more.

However, the tap location isn't ideal because it gives us screen coordinates rather than map coordinates. To fix that, we need a MapReader view around our map, so we can convert between the two types of coordinates.

Change your code to this:

MapReader { proxy in
    Map(initialPosition: startPosition)
        .onTapGesture { position in
            if let coordinate = proxy.convert(position, from: .local) {
                print("Tapped at \(coordinate)")
            }
        }
}

Where things get interesting is how we place locations on the map. We’ve bound the location of the map to a property in ContentView, but now we need to send in an array of locations we want to show.

This takes a few steps, starting with a basic definition of the type of locations we’re creating in our app. This needs to conform to a few protocols:

  • Identifiable, so we can create many location markers in our map.
  • Codable, so we can load and save map data easily.
  • Equatable, so we can find one particular location in an array of locations.

In terms of the data it will contain, we’ll give each location a name and description, plus a latitude and longitude. We’ll also need to add a unique identifier so SwiftUI is happy to create them from dynamic data.

So, create a new Swift file called Location.swift, giving it this code:

struct Location: Codable, Equatable, Identifiable {
    let id: UUID
    var name: String
    var description: String
    var latitude: Double
    var longitude: Double
}

Storing latitude and longitude separately gives us Codable conformance out of the box, which is always nice to have. We’ll add a little more to that shortly, but it’s enough to get us moving.

Now that we have a data type where we can store an individual location, we need an array of those to store all the places the user wants to visit. We’ll put this into ContentView for now just we can get moving, but again we’ll return to it shortly to add more.

So, start by adding this property to ContentView:

@State private var locations = [Location]()

Next, we want to add a location to that whenever the onTapGesture() is triggered, so replace its current code with this:

if let coordinate = proxy.convert(position, from: .local) {
    let newLocation = Location(id: UUID(), name: "New location", description: "", latitude: coordinate.latitude, longitude: coordinate.longitude)
    locations.append(newLocation)
}

Finally, update ContentView so we create markers from each of the locations in the array:

Map(initialPosition: startPosition) {
    ForEach(locations) { location in
        Marker(location.name, coordinate: CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude))
    }
}

That’s enough map work for now, so go ahead and run your app again – you should be able to move around as much as you need, then add locations wherever you see fit.

I know it took a fair amount of work to get set up, but at least you can see the basics of the app coming together!

Hacking with Swift is sponsored by Superwall.

SPONSORED Superwall lets you build & test paywalls without shipping updates. Run experiments, offer sales, segment users, update locked features and more at the click of button. Best part? It's FREE for up to 250 conversions / mo and the Superwall team builds out 100% custom paywalls – free of charge.

Learn More

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI 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 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 Beyond Code

Was this page useful? Let us know!

Average rating: 4.8/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.