Skip to content

fix(ios): keep setEdgeInsets insets when other props change - #100

Merged
lodev09 merged 2 commits into
mainfrom
eric/fix-ios-edge-insets-prop-reset
Oct 18, 2026
Merged

lodev09 merged 2 commits into
mainfrom
eric/fix-ios-edge-insets-prop-reset

Conversation

@erickreutz

Copy link
Copy Markdown
Member

On iOS, insets set with the setEdgeInsets ref method reset whenever any other MapView prop changes, such as theme, mapType, userLocationEnabled, or initialCoordinate. The map snaps back to the edgeInsets prop instantly, with no animation and no camera refit, so content framed above a bottom sheet or below a header slides under it. Common triggers are a system theme change and location permission arriving after the insets were set. We hit it in the Lugg consumer app, where a light/dark switch pushed route pins behind the status bar. Android is not affected because its prop setters only run for changed keys.

The cause is in -[LuggMapView updateProps:oldProps:]. It compared the incoming edgeInsets prop with _edgeInsets, which the setEdgeInsets command also writes. Fabric builds each new props object from the previous one, and convertRawProp keeps sourceProps.edgeInsets for keys that did not change, so every unrelated update carries the old edgeInsets value. Once the command moved _edgeInsets away from that value, each update looked like an edgeInsets change and re-applied the stale prop through the provider's two-argument setEdgeInsets:oldEdgeInsets:, which applies with no duration.

The fix keeps the last applied prop in _edgeInsetsProp and applies the prop only when that value changes. The last write still wins: a new edgeInsets value replaces insets set by the command, and a later command replaces the prop. prepareForRecycle resets _edgeInsets and _edgeInsetsProp to zero, matching initWithFrame. Without that reset, a recycled view would keep the previous map's command-set insets whenever its new prop matched the old one. React Native keeps _props across recycling, so diffing for the other props is unchanged.

Reproduced in the bare example on an iOS 26.5 simulator with Apple Maps: the Home screen sets the bottom inset from the control sheet with setEdgeInsets, and switching the map type shifted the map down by half the sheet height and moved the Apple attribution behind the sheet. With this change the map stays put. Hiding the map and then opening Static Maps confirms the recycled view does not inherit the inset.

updateProps compared the edgeInsets prop with _edgeInsets, which the
setEdgeInsets command also writes. Fabric copies unchanged props from the
previous props, so any unrelated prop update (theme, mapType,
userLocationEnabled, ...) carried the old edgeInsets value and re-applied
it, instantly undoing insets set by the command.

Track the last applied prop in _edgeInsetsProp and apply the prop only
when it changes. Reset both values in prepareForRecycle so a recycled
view does not inherit the previous map's insets.
@vercel

vercel Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
lugg-maps Ready Ready Preview Oct 18, 2026 3:55pm UTC

Request Review

- iOS: apply prop changes through setEdgeInsets (duration 0) so a running
  Apple inset animation stops; drop unused _oldEdgeInsets
- iOS Apple: start from in-flight insets when interrupting an animation
- iOS static: staticKey change restores prop insets
- web: compare edgeInsets prop with last prop, not command-written insets
- docs + skill: document prop vs setEdgeInsets precedence
@lodev09

lodev09 commented Oct 18, 2026

Copy link
Copy Markdown
Member

Thanks @erickreutz. The core fix works. I reproduced the original bug on main (bare example, iPhone 17 Pro Max sim, iOS 26.5, Apple Maps): after setEdgeInsets({ bottom: 300 }), toggling theme or mapType snapped the map back to the zero prop. On this branch the insets stay.

The review found three more cases, all reproduced, and I pushed fixes in 2a98b4c.

1. iOS (Apple): a prop change during an animated setEdgeInsets gets lost

The prop path called the two-argument setEdgeInsets:oldEdgeInsets:. That call never stops the CADisplayLink, so the animation finished and overwrote the prop. It also used the command's target as the old insets, not the insets the map was actually at, so the camera moved by the wrong amount.

  • Repro: setEdgeInsets({ bottom: 400 }, { duration: 1500 }), then change the prop to { top: 200 } after 300 ms.
  • Before: the map ended at bottom: 400, and the marker was 100pt away from where it should be. Later prop changes kept drifting (about 200pt cumulative).
  • After: insets top: 200, and the marker sits on the expected center.

2. Web has the same bug

The effect compared the prop with prevEdgeInsets, which the ref method also writes.

  • Repro: setEdgeInsets({ bottom: 300 }), then a parent re-render that changes no map prop.
  • Before: the marker went from −150px back to 0.
  • After: it stays at −150. A later prop change wins (+100), and a later command wins again (−150).

3. iOS static maps: command insets carry over to the next item after staticKey changes

Before this PR, the stale prop re-apply reset the insets as a side effect. Now they stay, so a reused list row rendered (and cached) the new item with the previous item's insets.

  • Repro: setEdgeInsets({ bottom: 200 }), then change staticKey and initialCoordinate.
  • Before: the marker was 100pt high.
  • After: the marker is centered.

Changes in 2a98b4c

  • LuggMapView.mm: a prop change now goes through setEdgeInsets:left:bottom:right:duration: with duration:0. Removed the unused _oldEdgeInsets. resetStaticContent restores _edgeInsetsProp.
  • AppleMapProvider.mm: interrupting a running animation now starts from _edgeInsetsCurrent.
  • MapView.web.tsx: the effect compares the prop with the last prop value, held in a new prevEdgeInsetsProp ref.
  • Docs and skill: documented that the last write wins between the prop and the command, and what a staticKey change does. Added a troubleshooting entry.

Verified: typecheck, lint, tests, prettier and clang-format pass. All cases above were re-run on the iOS sim and in Chrome. Android was not tested.

Not fixed here: on web, changing theme makes @vis.gl/react-google-maps rebuild the map at initialCoordinate, which drops the camera position and inset offset. This existed before the PR.

@lodev09
lodev09 merged commit 51d8d8d into main Oct 18, 2026
5 checks passed
@lodev09
lodev09 deleted the eric/fix-ios-edge-insets-prop-reset branch October 18, 2026 16:03

This branch was successfully deployed

1 active deployment
Preview — 2a98b4c0 Deployed Oct 18, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants