Single notification delivery: map renders before add()
LiveContainer did not honour the replace-in-place contract: adding a second request with the same identifier delivered a SECOND notification instead of updating the first. Now the MKMapSnapshotter renders first (10s timeout), then exactly ONE request is added with the map attached. If the snapshot fails or times out, the notification still fires without the image — never two notifications.
This commit is contained in:
@@ -209,13 +209,13 @@ final class ProximityMonitor: NSObject, ObservableObject, @preconcurrency CLLoca
|
||||
]
|
||||
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.
|
||||
// Render the map FIRST, then deliver ONE notification with the map
|
||||
// attached. (Earlier, delivering immediately and "replacing" via a
|
||||
// second add() showed up as two notifications in LiveContainer — the
|
||||
// replace-in-place contract isn't honoured there.) A timeout
|
||||
// guarantees the alert still fires, without the map, if the snapshot
|
||||
// hangs.
|
||||
mapStatus?("map: preparing…")
|
||||
UNUserNotificationCenter.current().add(
|
||||
UNNotificationRequest(identifier: identifier, content: content, trigger: nil)
|
||||
)
|
||||
|
||||
let options = MKMapSnapshotter.Options()
|
||||
options.region = MKCoordinateRegion(
|
||||
@@ -226,24 +226,28 @@ final class ProximityMonitor: NSObject, ObservableObject, @preconcurrency CLLoca
|
||||
options.mapType = .standard
|
||||
let snapshotter = MKMapSnapshotter(options: options)
|
||||
|
||||
// Timeout so a hung snapshot can't leave the result line stuck on
|
||||
// "preparing" forever.
|
||||
var finished = false
|
||||
let deliver: (String) -> Void = { status in
|
||||
guard !finished else { return }
|
||||
finished = true
|
||||
mapStatus?(status)
|
||||
UNUserNotificationCenter.current().add(
|
||||
UNNotificationRequest(identifier: identifier, content: content, trigger: nil)
|
||||
)
|
||||
}
|
||||
|
||||
// Timeout so a hung snapshot can't leave the alert undelivered (and
|
||||
// the result line stuck on "preparing") forever.
|
||||
let timeout = DispatchWorkItem {
|
||||
Task { @MainActor in
|
||||
guard !finished else { return }
|
||||
finished = true
|
||||
mapStatus?("map: timed out")
|
||||
deliver("map: timed out — no image")
|
||||
}
|
||||
}
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 8, execute: timeout)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 10, 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,
|
||||
let image = Self.mapImage(snapshot: snapshot, station: station) {
|
||||
@@ -251,18 +255,13 @@ final class ProximityMonitor: NSObject, ObservableObject, @preconcurrency CLLoca
|
||||
.appendingPathComponent("alert-map-\(UUID().uuidString).png")
|
||||
if let data = image.pngData(), (try? data.write(to: url)) != nil,
|
||||
let attachment = try? UNNotificationAttachment(identifier: "map", url: url, options: nil) {
|
||||
finalContent.attachments = [attachment]
|
||||
content.attachments = [attachment]
|
||||
status = "map: attached"
|
||||
}
|
||||
} else if let error {
|
||||
status = "map: snapshot failed (\(error.localizedDescription))"
|
||||
}
|
||||
mapStatus?(status)
|
||||
// Replace the delivered notification (same identifier) so the
|
||||
// map appears in place.
|
||||
UNUserNotificationCenter.current().add(
|
||||
UNNotificationRequest(identifier: identifier, content: finalContent, trigger: nil)
|
||||
)
|
||||
deliver(status)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user