Map Dead End

Status: ready

Symptom

A map is the only way to choose a location: a store, a pickup point, a branch, a clinic.

Task:        choose the nearest open pickup point
Only path:   pan the map, tap pins, read callouts, compare from memory

Where it shows up

Delivery and pickup, ride sharing, bank branches, clinics, travel, real estate, events, parking.

Why it blocks the task

Exploring a map with VoiceOver means dragging a finger and hoping to hit a pin. Switch Control can't pan or pinch. Pins overlap, and distance and opening hours are only in callouts, so comparing three options means opening three callouts and remembering them. The user needs "the closest open point", not "a pin somewhere".

A map is one view of the data. It shouldn't be the only path through the task.

Root cause

The map was treated as the data source instead of one presentation of it.

  1. Pins have no names.
  2. Distance and hours are only in callouts.
  3. There's no list, or the list is read-only.
  4. Filters apply to the map but not to the list.
  5. The address is confirmed by dragging a pin.

Before

Map {
    ForEach(points) { point in
        Marker("", coordinate: point.coordinate)
    }
}

Fix

Verdict: a list of the same points next to the map, with the information that matters for the choice, and a selection action in each row. Pins keep their names.

Why: a list can be scanned, compared and selected with any input method. The map stays for users who want it.

Picker("View", selection: $mode) {
    Text("Map").tag(Mode.map)
    Text("List").tag(Mode.list)
}
.pickerStyle(.segmented)

if mode == .list {
    List(visiblePoints) { point in
        VStack(alignment: .leading) {
            Text(point.name).font(.headline)
            Text("\(point.distanceText) · \(point.hoursText)")
            Button("Pick up here") { select(point) }
                .accessibilityLabel("Pick up at \(point.name)")
                .accessibilityInputLabels(["Pick up here", "Pick up at \(point.name)"])
                .accessibilityAddTraits(selected == point ? .isSelected : [])
        }
    }
} else {
    Map {
        ForEach(visiblePoints) { point in
            Marker(point.name, coordinate: point.coordinate)
        }
    }
}
The Map and List selector is set to List, showing two pickup points with independent selection actions.
List alternative: two pickup points appear as labeled rows with separate selection buttons. Captured on iPhone 17 Pro Max, iOS 27.0.1.

Map and list use the same visiblePoints, with the same filters and sorting. If the map shows 5 points and the list shows 3, the user stops trusting both.

The visible text "Pick up here" isn't part of the label "Pick up at 8 Garden Street". Without the accessibilityInputLabels line, "Tap Pick up here" wouldn't match. The demo has the line.

For an address, the path is search → choose a result from a list → optional map preview → a text field for entrance notes. Not "drag the pin to the entrance".

Check

Profile:   VoiceOver
Task:      choose the nearest open pickup point
Expected:  done from the list, without touching the map
           each row reads name, distance, hours
           the selected point says "Selected"
Profile:   Switch Control
Task:      the same
Expected:  rows and filters are scan targets, no pan or pinch needed
Profile:   Voice Control
Task:      "Tap Pick up at 8 Garden Street"
Expected:  selects that point directly

WCAG mapping

  1. 1.3.1 Info and Relationships: spatial data has a structured equivalent.
  2. 2.5.1 Pointer Gestures: pan and pinch have alternatives.
  3. 2.1.1 Keyboard: the task can be done without pointer gestures.

App Store and product risk

A map-only pickup or branch choice blocks a core task. The user sees what the business offers and can't get to it.

  1. List Alternative
  1. Chapter 7
  2. Chapter 10
  3. Chapter 17

Sources

  1. Apple Developer: MapKit for SwiftUI
  2. Apple Human Interface Guidelines: Maps
  3. W3C: Understanding SC 2.5.1 Pointer Gestures