Deliver alert immediately; map attaches in place afterwards

The notification used to be added only inside the MKMapSnapshotter
completion — if the snapshot hung or failed, no notification was ever
delivered and the result line never showed the map status. Now the
notification fires immediately (same identifier), the snapshot renders
in the background with an 8s timeout, and on success the delivered
notification is replaced in place with the map attached. Result line
always updates: preparing -> attached / failed / timed out.
This commit is contained in:
FuelBoard Contributor
2026-08-12 10:12:35 +01:00
parent 9032c4a551
commit b56d2a129c
+33 -4
View File
@@ -207,6 +207,16 @@ final class ProximityMonitor: NSObject, ObservableObject, @preconcurrency CLLoca
"stationLat": station.lat,
"stationLng": station.lng,
]
let identifier = UUID().uuidString
// Deliver the notification IMMEDIATELY the alert must never wait on
// the map. If the snapshot later succeeds, the same identifier is used
// to replace the delivered notification with the map attached.
mapStatus?("map: preparing…")
UNUserNotificationCenter.current().add(
UNNotificationRequest(identifier: identifier, content: content, trigger: nil)
)
let options = MKMapSnapshotter.Options()
options.region = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: station.lat, longitude: station.lng),
@@ -215,8 +225,24 @@ final class ProximityMonitor: NSObject, ObservableObject, @preconcurrency CLLoca
options.size = CGSize(width: 800, height: 400)
options.mapType = .standard
let snapshotter = MKMapSnapshotter(options: options)
snapshotter.start { [content] snapshot, error in
// Timeout so a hung snapshot can't leave the result line stuck on
// "preparing" forever.
var finished = false
let timeout = DispatchWorkItem {
Task { @MainActor in
guard !finished else { return }
finished = true
mapStatus?("map: timed out")
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 8, execute: timeout)
snapshotter.start { [content] snapshot, error in
timeout.cancel()
Task { @MainActor in
guard !finished else { return }
finished = true
let finalContent = content
var status = "map: no image"
if let snapshot, error == nil,
@@ -232,8 +258,11 @@ final class ProximityMonitor: NSObject, ObservableObject, @preconcurrency CLLoca
status = "map: snapshot failed (\(error.localizedDescription))"
}
mapStatus?(status)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: finalContent, trigger: nil)
UNUserNotificationCenter.current().add(request)
// Replace the delivered notification (same identifier) so the
// map appears in place.
UNUserNotificationCenter.current().add(
UNNotificationRequest(identifier: identifier, content: finalContent, trigger: nil)
)
}
}
}
@@ -282,10 +311,10 @@ final class ProximityMonitor: NSObject, ObservableObject, @preconcurrency CLLoca
content.body = "\(candidate.name) · \(distanceText) away · \(String(format: "%.1fp", price)). Tap for directions."
content.sound = .default
let baseResult = "\(fuel.displayName) · \(brand) · \(String(format: "%.1fp", price)) · \(distanceText) away · radius \(unit.format(radiusKM))"
lastTestResult = baseResult
addAlertRequest(content: content, station: candidate) { status in
self.lastTestResult = "\(baseResult) · \(status)"
}
lastTestResult = baseResult
} else {
// No real candidate (no stations loaded yet, or none sell the
// monitored fuel) still fire so delivery is testable, but say