ReviewOS

craft-native/craft

MapMarker takes only a colour, so a map can show where things are but never who they are

#413
Closed glennmichael123 opened this 8 days ago ·
8 days ago

MapMarker takes a colour and a title and nothing else, so a map can show where things are but never who or what they are. Anything that needs a face, a logo, a number or a glyph on the map has to be left off it.

Tested on craft-native 0.0.293, both platforms.

What a marker can carry today

iOS, dist/ios/templates/CraftNativeScreen.swift:1558:

if type == "mapmarker" || type == "marker" {
    guard let point = points.first else { continue }
    nextMarkers[id] = (point, childProps["title"] as? String)
    if let color = childProps["pinColor"] as? String { nextPinColors[id] = Self.color(color) }
    if let bearing = Self.numeric(childProps["bearing"]), bearing.isFinite { … }
    …
}

geometry, title, pinColor, bearing, onPress. Android reads exactly the same set (dist/android/templates/CraftNativeMapGoogle.kt.template:131-166), so this is not a one-platform gap.

The view is then always built from a generated pin — CraftNativeScreen.swift:1869:

let color = (markerPinColors[id] ?? .systemRed).resolvedColor(with: traitCollection)
view.tintColor = color
view.image = Self.markerImage(color: color, direction: false)

There is no branch that accepts a supplied image, so no amount of prop-passing reaches view.image.

Where it bit

A live board for a backyard ultra: every runner goes round the same loop from the same gun, and the screen draws the loop with each runner placed on it. The thing that makes it worth looking at is seeing who is where — the leader, your friend, the one about to miss the cutoff.

On the web the same feature draws each runner's avatar on the loop. On Craft the best available is a coloured pin, so four states had to be squeezed into four colours, with the identity pushed into the title where it is invisible until tapped:

webCraft
on their own measured paceavatar, solid ringgreen pin
placed on the field's paceavatar, dashed + dimmedpale green pin
past their own lap timeavatar, red ringred pin
resting in the corralavatar + +N countgrey pin

Four runners on a loop are four identical dots differing only in hue. The feature works; it just stops being the thing people open it for.

Precedent in Craft's own API

The chrome API already does exactly this. tabItems[].image accepts an https URL and is used for the account's photo — CraftApp.swift:774:

if item.keys.contains("image") {
    next.image = (item["image"] as? String).flatMap { URL(string: $0)?.scheme == "https" ? $0 : nil }
}

So the https-only-remote-image rule, the fetch and the null fallback all exist already; markers just do not get them.

Suggested shape

<MapMarker :geometry="[lat, lng]" image="https://…/avatar.jpg" :imageSize="30" imageShape="circle" title="Ada" />
  • image: https URL only, same validation as tabItems[].image; falls back to the current generated pin when absent, unreachable or non-https, so nothing regresses
  • imageSize: points, clamped to something sane
  • imageShape: circle | square, since a circular crop is what nearly every avatar-on-a-map wants and doing it caller-side is impossible

A smaller version that would still solve most of it: a glyph / symbol prop drawing an SF Symbol or one or two characters inside the existing pin. That would at least let markers be told apart without a tap.

Also worth knowing

Non-bearing markers are pinned at displayPriority = .required, collisionMode = .rectangle and zPriority = .max (CraftNativeScreen.swift:1871-1876). So markers at or near the same point all draw, all at the same z, and the caller cannot say which should be on top. For this screen that means the runner about to miss the cutoff can land underneath the group resting on the start line, with no way to order them. Happy to split that into its own issue if you would rather keep them separate.

6 days ago

Shipped in v0.0.369: @craft-native/ios and @craft-native/android both publish the marker-photo implementation from aa11b43, d5ac78f and 59e705f.

MapMarker.image accepts HTTPS photos, imageSize defaults to 30 logical points and clamps to 16–96, and imageShape supports circle/square. Invalid or failed sources retain the existing pin/direction fallback. Mutation preserves marker identity and tap IDs; stale photo loads cannot replace a newer source. Both Android provider adapters consume the shared photo loader.

Verified exact release commit 447523f: Mobile E2E run https://github.com/craft-native/craft/actions/runs/40491395933 passed. Its retained Android API 34 and 35 XML reports each show 84 tests, zero failures/errors/skips, including markerPhotosCropClampAndKeepTapIdentityAcrossMutations and markerPhotoPropsAcceptCompilerSpellingsAndRejectUnsafeAddresses. The iOS UIKit log explicitly records testMapMarkerPhotoCropsUpdatesAndFallsBackWithoutReplacingAnnotation passing. Android device pixel/tap evidence is for MapLibre; this does not claim a Google Maps credential-backed device run.

Downloaded npm 0.0.369 tarballs: CraftNativeScreen.swift and the Android MainActivityNative, MapLibre and Google templates SHA-256-match the tested release-tag sources. Publication and downloaded-binary acceptance succeeded: https://github.com/craft-native/craft/actions/runs/40491396336 . Marker stacking priority remains outside this photo feature; no configurable z-order is claimed.

Sign in to comment on this issue.