Image generated with ChatGPT

Map in SwiftUI (episode III — Look Around)

Nicola De Filippo
4 min readJan 16, 2024

In this episode, we’ll learn how to use the LookAround feature in our applications. To summarize, we will cover:

  1. How to select a marker on the map
  2. How to display the 3D landscape of the selected marker

Starting from the end, we aim for this:

And by tapping on Look Around:

We’ll use the same points as in the previous example [https://nicoladefilippo.com/map-in-swiftui-episode-ii/], and we’ll also incorporate a Sheet for detailed explanations [https://nicoladefilippo.com/swiftui-sheets-demystified-episode-i/].

Select a Marker

struct ContentView: View {
@State var position: MapCameraPosition = .region(MKCoordinateRegion(
center: .init(latitude: 37.554169, longitude: -122.313057),
span: .init(latitudeDelta: 0.7, longitudeDelta: 0.7)
))

@State var placeSelected: Int?

var places = [Place(name: "Berkeley", coordinates: CLLocationCoordinate2D(latitude: 37.8715, longitude: -122.2730)), Place(name: "Stanford", coordinates: CLLocationCoordinate2D(latitude: 37.4280, longitude: -122.1697))]

var body: some View {
VStack{
Map(position: $position, selection: $placeSelected) {
ForEach(Array(places.enumerated()), id: \.offset) { index, place in
Marker(place.name, coordinate: place.coordinates)
.tint(.orange)
}
}
}
}

Let’s begin by setting the position (the initial center of the map) in San Mateo, CA. We’ll declare a variable placeSelected that will contain the index of the selected marker, and an array of places, where each place is defined as follows:

struct Place: Identifiable {
let id = UUID()
var name: String
var coordinates: CLLocationCoordinate2D = .init(latitude: 0.0, longitude: 0.0)
}

The placeSelected variable is optional because it can assume a nil value, not only at the beginning but also when tapping on different places on the map apart from the markers.

Therefore, the map is declared by passing the initial position and the selectedPlace.

If you noticed, in the second image the selected balloon is larger. If we also want to change its color, we can use a function like this:

func getColor(_ index: Int, _ selected: Int?) -> Color {
if selected != nil && selected! == index{
return .blue
}
return .orange
}

…and use it in the tint setting:

Marker(place.name, coordinate: place.coordinates)
.tint(getColor(index, placeSelected))

Show the 3D landscape of the selected marker

First, let’s create the sheet that contains a preview of the LookAround feature:

struct SheetUIView: View {
@State private var lookAroundScene: MKLookAroundScene?
var selectedPlace: Place
var body: some View {
LookAroundPreview(initialScene: lookAroundScene)
.onAppear {
lookAroundScene = nil
Task {
let request = MKLookAroundSceneRequest(coordinate: selectedPlace.coordinates)
lookAroundScene = try? await request.scene
}
}
}
}

This view declares a lookAroundScene that contains the scene. This variable is set by calling a request to MKLookAroundSceneRequest. This operation is executed in a Task to avoid freezing the application.

Now, let’s see how to use this view:

@State var isPresented: Bool = false
var body: some View {
VStack{
Map(position: $position, selection: $placeSelected) {
ForEach(Array(places.enumerated()), id: \.offset) { index, place in
Marker(place.name, coordinate: place.coordinates)
.tint(.orange)
}
}
}.sheet(isPresented: $isPresented, content: {
SheetUIView(selectedPlace: places[placeSelected!])
.presentationDetents([.fraction(0.4)])
})
.onChange(of: placeSelected) {
guard placeSelected != nil else { return }
isPresented = true
}
}

First, we added an isPresented variable; when it’s true, the sheet is displayed. Using .presentationDetents([.fraction(0.4)]), we set the height of the sheet to 40% of the screen height. This view receives the selected place as a parameter.

The event of placeSelected being changed is captured by onChange. Here, we verify that the variable is not nil, and only in this case isPresented becomes true, causing the sheet to appear.

Another method to present the LookAround feature could be as follows

var body: some View {
VStack{
Map(position: $position, selection: $placeSelected) {
ForEach(Array(places.enumerated()), id: \.offset) { index, place in
Marker(place.name, coordinate: place.coordinates)
.tint(.orange)
}
}
}.safeAreaInset(edge: .bottom) {
HStack {
Spacer()
VStack(spacing: 0) {
if isPresented {
SheetUIView(selectedPlace: places[placeSelected!])
.frame(height: 128)
.clipShape(RoundedRectangle(cornerRadius: 10))
.padding([.top, .horizontal])
}
}
}
Spacer()
}
.onChange(of: placeSelected) {
guard placeSelected != nil else { return }
isPresented = true
}
}

This time, we display the preview of the LookAround feature at the bottom of the screen, not using a sheet dialog, but by inserting the SheetView into the safe area. Here, the view has rounded corners and a fixed height, as shown in the screen:

That’s all for this episode.

(originally published https://nicoladefilippo.com/map-in-swiftui-episode-iii-look-around/)

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Nicola De Filippo
Nicola De Filippo

Written by Nicola De Filippo

Software Engineer and Entrepreneur

No responses yet

Write a response