Overview

Look Around gives users a street-level, 360-degree view of a location without leaving your app. MapKit exposes it through MKLookAroundSceneRequest (async fetch), LookAroundPreview (interactive SwiftUI view), and MKLookAroundSnapshotter (static image for thumbnails). Coverage is limited to Apple-mapped areas; your code must degrade gracefully when a coordinate has no scene. For selecting the coordinate to preview from search results, see mapkit-search; for driving the camera to the selected location, see mapkit-camera.

Fetch a scene with MKLookAroundSceneRequest before showing the preview

MKLookAroundSceneRequest is async and throws. It returns nil when no Look Around imagery exists for the coordinate, and throws when the request fails (network error, rate limit). Handle both.

@State private var scene: MKLookAroundScene?
@State private var hasLookAround = false
 
func fetchScene(for coordinate: CLLocationCoordinate2D) async {
  do {
    scene = try await MKLookAroundSceneRequest(coordinate: coordinate).scene
    hasLookAround = scene != nil
  } catch {
    scene = nil
    hasLookAround = false
  }
}

Trigger this in .task(id: coordinate) so the fetch cancels and restarts whenever the selected coordinate changes.

Display LookAroundPreview only after a scene is confirmed

Show the LookAroundPreview view only when scene is non-nil. Showing a placeholder or “No Look Around available” label when coverage is absent avoids a blank gray rectangle.

Group {
  if let scene {
    LookAroundPreview(scene: Binding(get: { scene }, set: { _ in }), allowsNavigation: true)
      .frame(height: 200)
      .clipShape(RoundedRectangle(cornerRadius: 12))
  } else if hasLookAround == false && coordinate != nil {
    Label("No street-level imagery available", systemImage: "eye.slash")
      .foregroundStyle(.secondary)
      .frame(height: 200)
  }
}

Drive the preview from a selected annotation or search result

Bind scene fetching to the selected place in a detail sheet. Cancel the previous request by using .task(id:) with the place’s coordinate as the identity value.

.sheet(item: $selectedPlace) { place in
  VStack {
    LookAroundPreview(scene: $scene, allowsNavigation: false)
      .frame(height: 180)
      .task(id: place.coordinate) {
        scene = try? await MKLookAroundSceneRequest(coordinate: place.coordinate).scene
      }
    PlaceDetailContent(place: place)
  }
}

Pass allowsNavigation: false in a compact sheet so the user cannot wander far from the selected place; use true in a full-screen modal.

Generate static thumbnails with MKLookAroundSnapshotter

A list of places can show a tiny Look Around thumbnail for each row without the full interactive renderer. Use MKLookAroundSnapshotter to produce a UIImage.

func thumbnail(scene: MKLookAroundScene, size: CGSize) async throws -> UIImage {
  let options = MKLookAroundSnapshotter.Options()
  options.size = size
  options.pointOfInterestFilter = .includingAll
  let snapshotter = MKLookAroundSnapshotter(scene: scene, options: options)
  return try await snapshotter.snapshot.image
}

Cache the resulting UIImage keyed by coordinate in memory (NSCache) and on disk to avoid re-fetching on scroll. The snapshotter call is network-bound and should not repeat for a coordinate you have already imaged.

Preload scenes for nearby places to avoid latency

When a user is browsing a list of search results, prefetch MKLookAroundScene objects for the top few results in the background so the preview appears immediately when they tap.

func prefetchScenes(for places: [MKMapItem]) async {
  await withTaskGroup(of: (MKMapItem, MKLookAroundScene?).self) { group in
    for place in places.prefix(5) {
      group.addTask {
        let scene = try? await MKLookAroundSceneRequest(coordinate: place.placemark.coordinate).scene
        return (place, scene)
      }
    }
    for await (place, scene) in group {
      sceneCache[place.placemark.coordinate] = scene
    }
  }
}

Limit prefetch to five or fewer items. Each MKLookAroundSceneRequest makes a network request; prefetching too aggressively exhausts connections and slows the primary search.

Respect coverage gaps without degrading the overall UX

Not every street has Look Around coverage. Cities outside the US, Canada, Europe, and Japan are often uncovered. Design the feature as an enhancement, not a core requirement.

  • Default to showing the standard map card for a place; promote to Look Around when available.
  • Do not gate core functionality (directions, open-in-maps) on Look Around availability.
  • Show the same fallback UI consistently; do not display a broken empty rectangle.

Release scene references when the detail view dismisses

MKLookAroundScene holds a reference to cached imagery buffers. Nil out scene when the presenting view disappears to free memory promptly.

.onDisappear {
  scene = nil
}

The .task(id:) modifier cancels the fetch automatically on disappear, but an already-fetched scene stays in memory until you release the reference.