SwiftUI Blog

Mastering SwiftUI: Your Guide to Building Beautiful, Intuitive Apps.

Map – episode III: Look Around

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:

Now you can navigate in 3D.

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.

Leave a Reply

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