Implementing Look Around with MapKit in SwiftUI

Implementing Look Around with MapKit in SwiftUI

Learn how to provide an interactive 3d street-level experience within your SwiftUI app.

When it comes to providing geographical information using the MapKit framework, users can access the Look Around feature, which provides an interactive 3D street-level experience. In this article, we will see how we can integrate the Look Around API for our SwiftUI apps.

We will start by defining a simple class that will control the scene that needs to be displayed using Look Around.

import MapKit

@Observable
class LookAroundManager {
    var lookAroundScene: MKLookAroundScene?
    var coordinate: CLLocationCoordinate2D?
}

This LookAroundManager class has two properties:

  • lookAroundScene: a variable that will store the information to retrieve and display a specific Look Around location’s imagery.
  • coordinate: a variable that will store a CLLocationCoordinate2D object containing the latitude and longitude values.

What we need now is a method that is responsible for creating a request object with the coordinates and taking the scene from the request. We can define it within the LookAroundManager class.

@Observable
class LookAroundManager {

    var lookAroundScene: MKLookAroundScene?
    var coordinate: CLLocationCoordinate2D?
    
    func loadPreview() async {
        Task {
            if let coordinate = coordinate {
                let request = MKLookAroundSceneRequest(coordinate: coordinate)
                do {
                    lookAroundScene = try await request.scene
                } catch (let error) {
                    print(error)
                }
            }
        }
    }
    
}

The loadPreview() method defined in the example above is an async method because the request needs to be created in a concurrent environment. Within the Task closure, we assign the scene value from the request object to our lookAroundScene property.

Now that we have everything set, we can use this class in a View to display the LookAroundPreview on our apps.

import SwiftUI
import MapKit

struct ContentView: View {

    @State var lookAroundManager = LookAroundManager()
    
    var body: some View {
        VStack {
            Text("Apple Park")
                .font(.title)
                .fontDesign(.serif)
                
            if let lookAroundScene = lookAroundManager.lookAroundScene {
                LookAroundPreview(initialScene: lookAroundScene)
                    .clipShape(RoundedRectangle(cornerRadius: 25))
                    .frame(height: 300)
                    .padding()
            } else {
                ContentUnavailableView("Look Around Preview not available", systemImage: "mappin.and.ellipse")
            }
        }
        
        .task {
            lookAroundManager.coordinates = CLLocationCoordinate2D(
                latitude: 37.334606,
                longitude: -122.00585
            )
            
            await lookAroundManager.loadPreview()
        }
    }
    
}
0:00
/0:27

In addition to the LookAroundPreview object, we can also use the lookAroundViewer(isPresented:initialScene:) modifier that will present the look around visualization as a modal:

import SwiftUI
import MapKit

struct ContentView: View {

    @State var lookAroundManager = LookAroundManager()
    @State var isLookingAround = false
    
    var body: some View {
        VStack {
            Text("Apple Park")
                .font(.title)
                .fontDesign(.serif)
        
            if let lookAroundScene = lookAroundManager.lookAroundScene {
                RoundedRectangle(cornerRadius: 25)
                    .padding()
                    .frame(height: 300)
                Button("Show Look Around"){
                    isLookingAround.toggle()
                }
            }
        }
        
        .lookAroundViewer(
            isPresented: $isLookingAround,
            initialScene: lookAroundManager.lookAroundScene
        )
        
        .task {
            lookAroundManager.coordinates = CLLocationCoordinate2D(
                latitude: 40.836639, 
                longitude: 14.306602
            )
            
            await lookAroundManager.loadPreview()
        }
    }
    
}
0:00
/0:20

Customization options

The Look Around feature in MapKit offers several customization options through its preview and viewer modifiers. You can control the navigation behavior by setting the allowsNavigation parameter - when set to false, it keeps the view fixed at the specified coordinates, while if set to true, it enables free navigation.

The visibility of road labels can be toggled using the showsRoadLabels parameter, letting you choose whether street names appear in the view.

Additionally, you can filter which points of interest appear during navigation by passing a PointsOfInterestCategories object to the pointsOfInterest property, giving you precise control over the types of locations highlighted in the Look Around experience.

import SwiftUI
import MapKit

struct ContentView: View {

    @State var lookAroundManager = LookAroundManager()
    
    var body: some View {
        VStack {
            Text("Apple Park")
                .font(.title)
                .fontDesign(.serif)
            
            if let lookAroundScene = lookAroundManager.lookAroundScene {
                LookAroundPreview(
                    initialScene: lookAroundScene,
                    allowsNavigation: false,
                    showsRoadLabels: false,
                    pointsOfInterest: .all,
                    badgePosition: .topTrailing
                )
                .clipShape(RoundedRectangle(cornerRadius: 25))
                .frame(height: 300)
                .padding()
            } else {
                ContentUnavailableView("Look Around Preview not available", systemImage: "mappin.and.ellipse")
            }
        }
        
        .task {
            lookAroundManager.coordinates = CLLocationCoordinate2D(
                latitude: 37.334606,
                longitude: -122.00585
            )
            
            await lookAroundManager.loadPreview()
        }
    }
    
}