mapbox / mapbox/mapbox-maps-ios

Standard style renders one frame with broken 3D-light state on first hard zoom-out (once per process, device only)

Open
#2,429 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

auto-triaged bug :beetle:
Dominant language
Swift
Stars
601
Forks
196
PR merge metrics
No merged PRs in 30d

Description

## Environment

- Xcode version: 17F113 (Xcode 17.x)
- Maps SDK Version: 11.26.0 (SwiftUI `Map`)
- iOS version: 26.5.2
- Devices affected: iPhone 15 Pro (reproduced); **not reproducible in the iOS Simulator**

## Observed behavior

Exactly **once per app process**, the first fast zoom-out from street level (~z17) toward region level renders **a single frame with broken 3D-light state**:

- With `lightPreset: .night` the frame renders near-white (a "white flash" over a dark map).
- With `lightPreset: .day` the same glitch renders **dark** instead.

The inversion with light preset suggests the frame is drawn with the Standard style's lighting missing/defaulted. The glitch never repeats within the same process — every later zoom-out is clean. On a 120Hz device the frame lasts ~8ms; 60fps screen recordings often miss it entirely (we caught it with a 120fps recording and per-frame luma analysis: the glitch frame's average luma was 235/255 against a ~50 baseline for surrounding frames).

Verified by frame capture: UIKit/SwiftUI overlays and view annotations composite normally over the glitch frame — only the map surface is affected. No style events fire around it (`onStyleLoaded` fires exactly once, at startup).

Tested one variable at a time, each in a fresh process (required, since the glitch is once-per-process):

| Variable | Result |
|---|---|
| theme `.faded` vs `.default` | flashes either way |
| lightPreset `.night` vs `.day` | flashes either way, color inverts |
| `presentationTransactionMode` `.sync` / `.async` / `.automatic` | flashes in all |
| bare `Map` with no annotations/content | still flashes |
| pinch gesture vs programmatic `withViewportAnimation` | both trigger |
| iOS Simulator | never reproduces |

## Expected behavior

No frame should render without the configured light preset applied.

## Steps to reproduce

Run the view below on a **physical device** (fresh launch), and watch the first zoom-out:

```swift
import MapboxMaps
import SwiftUI

struct ReproView: View {
@State private var viewport: Viewport = .camera(
center: CLLocationCoordinate2D(latitude: 31.2304, longitude: 121.4737),
zoom: 10
)

var body: some View {
Map(viewport: $viewport) {}
.mapStyle(.standard(theme: .faded, lightPreset: .night, showPlaceLabels: false))
.ignoresSafeArea()
.task {
let center = CLLocationCoordinate2D(latitude: 31.2304, longitude: 121.4737)
try? await Task.sleep(for: .seconds(5))
while !Task.isCancelled {
withViewportAnimation(.easeIn(duration: 1.0)) {
viewport = .camera(center: center, zoom: 17)
}
try? await Task.sleep(for: .seconds(1.6))
withViewportAnimation(.easeOut(duration: 0.9)) {
viewport = .camera(center: center, zoom: 4)
}
try? await Task.sleep(for: .seconds(1.5))
}
}
}
}
```

The first zoom-out of the process flashes; subsequent cycles never do. Because a 60fps recording can miss the 8ms frame, verify with a 120fps capture or by eye on-device.

## Workaround

Running one hidden zoom-in/zoom-out cycle (z17 → z4) while the map is occluded consumes the glitch off-screen; after that the process never flashes again.

Happy to provide the frame captures / luma traces from our analysis if useful.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the SwiftUI ReproView on a physical iPhone 15 Pro, using a fresh app process and the provided first zoom-out sequence. Verify the behavior with a 120Hz capture, then compare the first cycle with later cycles and the simulator. Done means no frame renders without the configured light preset during the initial zoom-out.

Written by the indexing model from the issue text.

Assessment

Tech stack
ios, swift
Domain
computer-graphics, mobile
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
43/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.