Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion docs/content/docs/components/map-view.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ Notes:
instead of rendering the map again (markers and shapes stay live and
re-render from props). The camera and map settings are part of the
cache key automatically. Changing `staticKey` discards the current
image and re-renders. The cache is bounded by count and total memory.
image and re-renders, and insets set with `setEdgeInsets` give way to
the `edgeInsets` prop. The cache is bounded by count and total memory.
- On iOS (Google), the base map is only captured and cached once the SDK
reports the map stable with all tiles loaded. If tiles fail (e.g.
offline), the live warmup map stays in place instead and renders again
Expand Down Expand Up @@ -324,6 +325,11 @@ interface SetEdgeInsetsOptions {
}
```

Insets set this way stay in place when the map re-renders or other props
change. The last write wins: a new `edgeInsets` prop value replaces them
(instantly, also during an animation), and a later `setEdgeInsets` call
replaces the prop.

### reload

Load the map again, e.g. to recover from missing tiles after a network
Expand Down
21 changes: 15 additions & 6 deletions ios/LuggMapView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ @implementation LuggMapView {
double _minZoom;
double _maxZoom;
UIEdgeInsets _edgeInsets;
UIEdgeInsets _oldEdgeInsets;
UIEdgeInsets _edgeInsetsProp;
LuggMapViewInsetAdjustment _insetAdjustment;
}

Expand Down Expand Up @@ -284,9 +284,11 @@ - (void)startStaticContent {
// staticKey identifies the map's content; when it changes (e.g. a recycled
// list row bound to a new item), any live warmup or displayed snapshot is
// stale and the static lifecycle restarts. Without this, an in-flight
// warmup caches the old item's map under the new key.
// warmup caches the old item's map under the new key. Insets from the
// setEdgeInsets command belong to the old item, so the prop insets return.
- (void)resetStaticContent {
_staticSnapshotDone = NO;
_edgeInsets = _edgeInsetsProp;

if (_provider) {
[_provider destroy];
Expand All @@ -303,6 +305,8 @@ - (void)prepareForRecycle {
[super prepareForRecycle];

_staticSnapshotDone = NO;
_edgeInsets = UIEdgeInsetsZero;
_edgeInsetsProp = UIEdgeInsetsZero;

[_provider destroy];
_provider = nil;
Expand Down Expand Up @@ -546,10 +550,15 @@ - (void)updateProps:(Props::Shared const &)props
UIEdgeInsets newEdgeInsets = UIEdgeInsetsMake(
newViewProps.edgeInsets.top, newViewProps.edgeInsets.left,
newViewProps.edgeInsets.bottom, newViewProps.edgeInsets.right);
if (!UIEdgeInsetsEqualToEdgeInsets(newEdgeInsets, _edgeInsets)) {
_oldEdgeInsets = _edgeInsets;
_edgeInsets = newEdgeInsets;
[_provider setEdgeInsets:_edgeInsets oldEdgeInsets:_oldEdgeInsets];
// Compare with the last prop, not _edgeInsets: the setEdgeInsets command
// writes _edgeInsets, and unrelated prop updates carry the old edgeInsets
if (!UIEdgeInsetsEqualToEdgeInsets(newEdgeInsets, _edgeInsetsProp)) {
_edgeInsetsProp = newEdgeInsets;
[self setEdgeInsets:newEdgeInsets.top
left:newEdgeInsets.left
bottom:newEdgeInsets.bottom
right:newEdgeInsets.right
duration:0];
}

[super updateProps:props oldProps:oldProps];
Expand Down
5 changes: 5 additions & 0 deletions ios/core/AppleMapProvider.mm
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,11 @@ - (void)setEdgeInsets:(UIEdgeInsets)edgeInsets
- (void)setEdgeInsets:(UIEdgeInsets)edgeInsets
oldEdgeInsets:(UIEdgeInsets)oldEdgeInsets
duration:(double)duration {
// An interrupted animation leaves the map at its in-flight insets, not at
// the caller's last target
if (_edgeInsetsDisplayLink) {
oldEdgeInsets = _edgeInsetsCurrent;
}
[self stopEdgeInsetsAnimation];

if (duration != 0 && _mapView) {
Expand Down
2 changes: 1 addition & 1 deletion skills/maps-usage/references/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ All methods are synchronous fire-and-forget (`void`). They no-op until the nativ
|--------|-----------|-------------|
| `moveCamera` | `(coordinate: Coordinate, options?: { zoom?: number; duration?: number }) => void` | Center on a coordinate. `zoom` omitted/`0` keeps the current zoom. Heading and pitch are preserved |
| `fitCoordinates` | `(coordinates: Coordinate[], options?: { padding?: EdgeInsets; duration?: number }) => void` | Fit all points in view. Resets heading to north. `padding` is added on top of the current `edgeInsets`. A single coordinate delegates to `moveCamera` at `initialZoom`. Empty array is a no-op |
| `setEdgeInsets` | `(edgeInsets: EdgeInsets, options?: { duration?: number }) => void` | Shift the viewport (logical center, attribution, controls). Camera events report the inset-adjusted center |
| `setEdgeInsets` | `(edgeInsets: EdgeInsets, options?: { duration?: number }) => void` | Shift the viewport (logical center, attribution, controls). Camera events report the inset-adjusted center. Survives re-renders; last write wins vs. the `edgeInsets` prop (a changed prop value replaces it, a later call replaces the prop) |
| `reload` | `() => void` | Rebuild the map at the current coordinate and zoom. Live map: heading/pitch reset, `onReady` fires again, children remain. Static map: re-renders the base map and drops the cached `staticKey` image (iOS) or recreates the lite map (Android). Preserves `mapType` and edge insets |

`duration` (ms): `-1` (default) = platform default animation, `0` = instant, `> 0` = custom. Static maps ignore it.
Expand Down
2 changes: 1 addition & 1 deletion skills/maps-usage/references/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ export function App() {
| `initialZoom` | `number` | `10` | 🍎🅶🤖🌐 | Starting zoom (Google-style levels). Also the zoom `fitCoordinates` uses for a single coordinate |
| `minZoom` | `number` | — | 🍎🅶🤖🌐 | Lower zoom clamp |
| `maxZoom` | `number` | — | 🍎🅶🤖🌐 | Upper zoom clamp |
| `edgeInsets` | `EdgeInsets` | — | 🍎🅶🤖🌐 | Viewport insets. Shifts the logical center, attribution, and controls. Prefer `setEdgeInsets()` for animated changes |
| `edgeInsets` | `EdgeInsets` | — | 🍎🅶🤖🌐 | Viewport insets. Shifts the logical center, attribution, and controls. Prefer `setEdgeInsets()` for animated changes. Applied only when the value changes, so re-renders keep `setEdgeInsets()` insets |
| `insetAdjustment` | `'never' \| 'automatic'` | `'never'` | 🍎🅶🤖 | `'automatic'` adds safe-area insets to the map padding |

### Gestures
Expand Down
9 changes: 9 additions & 0 deletions skills/maps-usage/references/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Common issues with `@lugg/maps`, organized by symptom.
- [Marker animation janks or re-renders every frame](#marker-animation-janks-or-re-renders-every-frame)
- [Overlapping markers stack wrong](#overlapping-markers-stack-wrong)
- [Center pin / selected coordinate is offset under a sheet](#center-pin--selected-coordinate-is-offset-under-a-sheet)
- [Sheet insets reset when another prop changes](#sheet-insets-reset-when-another-prop-changes)
- [`e.nativeEvent.dragging` is undefined](#enativeeventdragging-is-undefined)
- [User location dot never appears](#user-location-dot-never-appears)
- [Static maps in a list are slow or crash the app](#static-maps-in-a-list-are-slow-or-crash-the-app)
Expand Down Expand Up @@ -118,6 +119,14 @@ Common issues with `@lugg/maps`, organized by symptom.

**Fix:** Keep the map full-size and push the viewport with `setEdgeInsets({ bottom: sheetHeight })`. Camera events then report the inset-adjusted center, and a pin translated up by `bottom / 2` sits exactly on it. See [Bottom sheet pattern](./advanced-patterns.md#bottom-sheet-with-edge-insets-and-center-pin).

## Sheet insets reset when another prop changes

**Symptom:** After `setEdgeInsets`, the map snaps back (content slides under the sheet or header) when `theme`, `mapType`, `userLocationEnabled`, or another prop changes, or on any re-render (web).

**Cause:** `@lugg/maps` ≤ 1.1.3 on iOS and web re-applied the unchanged `edgeInsets` prop over the command's insets.

**Fix:** Upgrade. Now the last write wins: only a changed `edgeInsets` value replaces command insets. On older versions, drive the sheet insets through the `edgeInsets` prop instead of the command.

## `e.nativeEvent.dragging` is undefined

**Cause:** The camera payload field is `gesture`, not `dragging`.
Expand Down
6 changes: 5 additions & 1 deletion src/MapView.web.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ export const MapView = memo(
const isDraggingRef = useRef(false);
const wasGesture = useRef(false);
const prevEdgeInsets = useRef(edgeInsets);
const prevEdgeInsetsProp = useRef(edgeInsets);
// Bumped by reload() to remount the map at the camera captured then
const [reloadState, setReloadState] = useState<{
key: number;
Expand Down Expand Up @@ -300,14 +301,17 @@ export const MapView = memo(
useEffect(() => {
if (!map || !edgeInsets) return;

const prev = prevEdgeInsets.current;
// Compare with the last prop, not prevEdgeInsets: the setEdgeInsets
// ref method writes prevEdgeInsets, and re-renders carry the old prop
const prev = prevEdgeInsetsProp.current;
const changed =
prev?.top !== edgeInsets.top ||
prev?.left !== edgeInsets.left ||
prev?.bottom !== edgeInsets.bottom ||
prev?.right !== edgeInsets.right;

if (changed) {
prevEdgeInsetsProp.current = edgeInsets;
applyEdgeInsets(edgeInsets);
}
}, [map, edgeInsets, applyEdgeInsets]);
Expand Down
Loading