---
title: "MapKit Look Around: Street-Level Previews and Scene Integration"
slug: "mapkit-look-around"
category: "ios"
tags: ["ios", "mapkit", "swift", "look-around", "street-view", "swiftui", "scene-snapshot"]
status: "stable"
last_updated: 2026-05-14
summary: "Integrating Apple Maps Look Around into a SwiftUI app: fetching scenes, displaying LookAroundPreview, generating snapshots, and handling coverage gaps."
related: ["[[ios/mapkit]]", "[[ios/mapkit-annotations]]", "[[ios/mapkit-camera]]", "[[ios/mapkit-search]]", "[[ios/swiftui]]", "[[coding/swift]]"]
---

## 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 [[ios/mapkit-search]]; for driving the camera to the selected location, see [[ios/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.

```swift
@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.

```swift
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.

```swift
.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`.

```swift
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.

```swift
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.

```swift
.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.

## Related

- [[ios/mapkit]]
- [[ios/mapkit-annotations]]
- [[ios/mapkit-camera]]
- [[ios/mapkit-search]]
- [[ios/mapkit-overlays]]
- [[ios/swiftui]]
- [[coding/swift]]
