feat(map): migrate from MapKit to MapLibre Native#253
Merged
Conversation
6b3df34 to
a8ac913
Compare
7404325 to
ebda046
Compare
Replace MapKit with MapLibre Native for all map views (main map, Line of Sight, Trace Path, LocationPicker, offline maps). Introduces a unified MC1MapView (UIViewRepresentable) with data-driven GeoJSON layers, sprite- based pin rendering, and raster tile overlays for satellite/topo styles. Key changes: - Add MC1MapView with clustered/fixed point layers and line style layers - Add PinSpriteRenderer for offline-capable map pin sprites - Add OfflineMapService and settings UI for offline map pack management - Migrate LOS, TracePath, ContactDetail, and LocationPicker to MC1MapView - Remove all legacy MapKit representables and annotation views - Split ChatConversationView into ChatView and ChannelChatView - Add MapLibre Native SPM dependency (maplibre-gl-native-distribution 6.23+)
…d of hardcoded miles
…ork-availability check
Extract 10 computed view properties from LineOfSightView.swift into separate View structs, reducing it from 1335 to 711 lines (~47%). Also collapse analysisSheetContent/analysisSheetVStack into a single property. Pure structural refactor with no behavior changes.
- Work around MapLibre bug where MLNEffectiveScaleFactorForView uses nativeBounds.width / bounds.width, which gives wrong scale in landscape because nativeBounds is fixed while bounds rotates with the device - Isa-swizzle the internal Metal UIView's setDrawableSize: to compute bounds × nativeScale instead of the buggy ratio - Use non-zero initial frame to avoid zero-size Metal init (issue #67) - Fix mapStyleChanged always false when style URL changes - Remove empty setupPointLayers stub, pass mapView param to setupRasterSources
- Use UIGraphicsImageRendererFormat.preferred() for all four renderers so sprites are rendered at the device's native screen scale instead of defaulting to 1x
- Debounce OfflineMapService pack reloads on per-tile progress notifications - Guard TracePathMapViewModel overlay rebuilds against GPS no-ops - Cache PinSpriteRenderer sprites across style reloads - Consolidate glass button style helpers into View+LiquidGlass, delete GlassButtonStyles.swift - Extract bounding-box calculation into setCameraRegion(fitting:) in TracePathMapViewModel - Add MapLayerID/MapSourceID constants, remove stringly-typed layer identifiers - Extract ContactType display properties to ContactType+Display.swift - Move CLLocationCoordinate2D.formattedString to Extensions/ - Remove unused openTopoMapB/C URL constants
- Intercept setContentScaleFactor: on MapLibre's Metal UIView alongside setDrawableSize: to prevent wrong contentsScale from being stored - Extract findMapView(from:) helper to deduplicate parent-walking logic - Track lastAppliedStyleURL instead of reading mapView.styleURL which MapLibre transiently nils during rotation - Guard data layer updates against mid-gesture state (isUserInteracting) - Guard label visibility updates against no-op changes - Guard against duplicate source/layer setup on style reload
- Build and cache mapPoints in rebuildMapPoints() to avoid reallocation on every SwiftUI body evaluation - Remove callout dismissal on camera region change - Regenerated L10n.swift
- Add OfflineMapLayer enum with base, satellite, and topo cases - Add offline style JSON files for satellite and topo layers - Extend OfflineMapService with multi-layer download, disk space checks, download speed tracking, error reporting, and database size - Update OfflineMapSettingsView with layer picker, error alerts, and storage footer - Add offlineMapLayer property to MapStyleSelection - Add localized strings for offline map layers across all languages
- Extract CLLocationCoordinate2D+BoundingRegion and ContactDTO+Coordinate extensions - Add pinStyle to ContactType+Display, removing duplicate logic from view models - Extract LabelsToggleButton, NorthLockButton, MapRefreshButton, and CenterAllButton into standalone views - Add isNorthLocked and setCameraRegion to MapViewModel - Remove colorScheme and mapPoints params from MapCanvasView - Delete GlassButtonModifier in favor of liquidGlassSecondaryButtonStyle - Add north-lock button localization strings
- Move showLabels, mapPoints, and mapLines to LineOfSightViewModel - Add didSet observers to rebuild map state on point/repeater changes - Extract AnalyzeButton into a standalone view - Extract HeightEditorGrid to deduplicate height editor layouts - Simplify PointHeightEditorView, RepeaterHeightEditorView, and RepeaterRowView
- Convert pathState from computed property to stored, rebuilt via rebuildPathState() - Add stored mapPoints rebuilt via rebuildMapPoints() - Make cameraRegionVersion private(set) with increment method - Trigger selective rebuilds via didSet observers on showLabels and repeatersWithLocation
- Remove "Downloaded" section header from packs list - Change pack status label from "Complete" to "Downloaded" - Remove unused offlineMaps.downloaded localization key
- Remove if/else that replaced the map with ContentUnavailableView when no contacts had location - Remove emptyState computed property and unused MapKit import - Remove map.emptyState and map.common.refresh L10n keys across all 9 languages - Remove corresponding EmptyState enum and Common.refresh from L10n.swift
ebda046 to
3eab6cb
Compare
- Added renderLabelSprite(text:) to PinSpriteRenderer, rendering pill background + text as a single UIImage - Extended renderOnDemand to handle "label-" prefix sprites on-demand, matching the existing hop-ring pattern - Updated configureNameLabelLayer to use per-feature pre-rendered sprites instead of MapLibre text rendering, eliminating the separate icon/text render pass that caused text from underlying labels to bleed through - Removed dead nameLabel feature attribute; predicates now use labelSpriteName - Extracted labelSpritePrefix constant shared by builder and recognizer
- Keep all trace path repeaters in the fixed (non-clustered) source so pins never migrate between MapLibre sources on tap, eliminating the async re-cluster gap that caused the blink - Return the rendered image from didFailToLoadImage so MapLibre uses it as an immediate fallback rather than skipping the frame
- MapLibre has no API for this, so we find its internal UILongPressGestureRecognizer (numberOfTapsRequired == 1, minimumPressDuration == 0) and disable it
- White casing layer behind each trace line style outlines each dash - Dash pattern values are scaled by the width ratio so casing and line stay in sync
- Resolve conflicts in AppState, TracePathMapView/ViewModel, TraceResultsSheet, and MapView - Delete MapKit PathLineOverlay/PathLineRenderer (replaced by MapLibre) - Adopt bestAvailableLocation in TracePathMapView onChange handler
…opRow - Replace removed TraceHop.signalLevel/signalColor static methods with SNRQuality computed properties to match dev's refactored API
…nSpriteRenderer Five offline map settings keys were English in all non-English locales. Translated for de, es, fr, nl, pl, ru, uk, zh-Hans. Added @mainactor to PinSpriteRenderer so the compiler enforces thread safety rather than trusting nonisolated(unsafe).
Swipe-to-delete was causing the row to disappear, reappear when the confirmation alert rendered, then disappear again after confirming. Delete directly on swipe instead.
…yles, guard download bounds Track last-applied points/lines separately from current coordinator state so data arriving mid-gesture gets pushed to MapLibre once the gesture ends. Disable all map styles when offline without a matching pack, not just satellite. Disable the download button until debounced bounds exist.
- Fix callout popover not dismissing when tapping badge text layers - Add name label layers to tap hit-test so tapping a name pill selects the contact - Re-render pin sprites on each style reload to fix stale colors after dark/light switch - Reset label visibility flag on style reload so user preference is reapplied - Derive mini-map camera version from coordinates so it recenters on refresh - Preserve user-paused offline downloads across app foreground cycles - Throw on nil style URL in offline download instead of silently skipping - Add NWPathMonitor onTermination handler for proper teardown - Add VoiceOver announcement and top padding on offline badge - Remove .controlSize(.small) from callout buttons for HIG tap targets - Allow re-analysis in Line of Sight after results exist - Use Measurement formatter for elevation display in LOS views - Add accessibility labels on comparison row triangle indicators - Fix refresh button layout shift and add accessibility label - Add safe area padding on trace path results banner - Extract computed property sub-views to View structs - Add OpenTopoMap b/c subdomain round-robin
- Add comparison row increased/decreased labels for VoiceOver - Add refresh button accessibility label - Add default path name fallback - Translations for all 9 languages - Regenerate L10n.swift via SwiftGen
- Add .gesturePinch to userGestureReasons so onCameraRegionChange fires during pinch-to-zoom, not just pan or double-tap zoom
Previous constants (2-12 KB/tile) reflected global averages skewed by empty ocean tiles. Populated land regions average 30-150 KB/tile at z10-14. Also add 500 KB overhead for non-tile resources (style JSON, sprites, glyph PBFs) that were previously excluded from the estimate.
- Use bestAvailableLocation in MapView and TracePathMapToolbarView - Recenter was silently failing when phone GPS denied but radio had a fix
- Fall back to Liberty style URL when offline (packs only cache that style, so dark mode went blank) - Thread isOffline through MC1MapView to field-use callers - Store pack bounds in OfflinePack, check viewport overlap in LayersMenu - Add MKCoordinateRegion → MLNCoordinateBounds conversion - Add L10n strings for disabled style hints (all 9 locales)
- Group callout content for VoiceOver, add hints to action buttons - Bump PointRowButtons icon from 16pt to 22pt for gloved use - Add isolated deinit to LineOfSightViewModel - Dismiss callout on camera move (stale anchor after rotation) - Use canonical SNRQuality instead of private 3-tier enum - Drop C-style %012d for Swift string padding - Use Measurement formatter in HeightEditorGrid instead of hardcoded "m" - Make TracePathEmptyState a card overlay instead of full-screen material - Document nonisolated(unsafe) NSExpression safety - Fix OfflineBadge trait, bump label sprite font to 12pt
b39584f to
a722d1a
Compare
- Apply .radioDisabled to telemetry, management, and room join buttons - Previously only the chat message button had the connection guard
- Add pathIndex property to MapLine instead of parsing from string ID - Replace hand-rolled UUID construction with UUID(hopIndex:) initializer
- Use withThrowingTaskGroup so multiple layers start simultaneously - Pre-compute regions and contexts before spawning tasks
- Dismiss overlay buttons on the map and LOS views had no accessibility label, making them invisible to VoiceOver - Added map.common.dismissOverlay key across all 9 languages
- Replace Text + Text("/s") with single interpolated Text in OfflinePackRow
- Add nonisolated(unsafe) to context capture in task group to fix a
sending-parameter error in Xcode 26.2's stricter region isolation checks
- Xcode 26.2's stricter sending-parameter checks reject captures from @mainactor context in group.addTask closures, even with nonisolated(unsafe) - Sequential download is sufficient for the typical 1-2 offline map layers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces MapKit with MapLibre Native across all map views (main map, Line of Sight, Trace Path, LocationPicker).
MC1MapView(UIViewRepresentable) using data-driven GeoJSON layers, sprite-based pins, and raster tile overlays for satellite/topo stylesOfflineMapServicewith settings UI for downloading and managing offline map packsPinSpriteRendererfor rendering map pin sprites without a network connectionTest plan