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
|
let identifier = UUID().uuidString
|
||||||
|
|
||||||
// Deliver the notification IMMEDIATELY — the alert must never wait on
|
// Render the map FIRST, then deliver ONE notification with the map
|
||||||
// the map. If the snapshot later succeeds, the same identifier is used
|
// attached. (Earlier, delivering immediately and "replacing" via a
|
||||||
// to replace the delivered notification with the map attached.
|
// 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…")
|
mapStatus?("map: preparing…")
|
||||||
UNUserNotificationCenter.current().add(
|
|
||||||
UNNotificationRequest(identifier: identifier, content: content, trigger: nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
let options = MKMapSnapshotter.Options()
|
let options = MKMapSnapshotter.Options()
|
||||||
options.region = MKCoordinateRegion(
|
options.region = MKCoordinateRegion(
|
||||||
@@ -226,24 +226,28 @@ final class ProximityMonitor: NSObject, ObservableObject, @preconcurrency CLLoca
|
|||||||
options.mapType = .standard
|
options.mapType = .standard
|
||||||
let snapshotter = MKMapSnapshotter(options: options)
|
let snapshotter = MKMapSnapshotter(options: options)
|
||||||
|
|
||||||
// Timeout so a hung snapshot can't leave the result line stuck on
|
|
||||||
// "preparing" forever.
|
|
||||||
var finished = false
|
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 {
|
let timeout = DispatchWorkItem {
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
guard !finished else { return }
|
deliver("map: timed out — no image")
|
||||||
finished = true
|
|
||||||
mapStatus?("map: timed out")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 8, execute: timeout)
|
DispatchQueue.main.asyncAfter(deadline: .now() + 10, execute: timeout)
|
||||||
|
|
||||||
snapshotter.start { [content] snapshot, error in
|
snapshotter.start { [content] snapshot, error in
|
||||||
timeout.cancel()
|
timeout.cancel()
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
guard !finished else { return }
|
|
||||||
finished = true
|
|
||||||
let finalContent = content
|
|
||||||
var status = "map: no image"
|
var status = "map: no image"
|
||||||
if let snapshot, error == nil,
|
if let snapshot, error == nil,
|
||||||
let image = Self.mapImage(snapshot: snapshot, station: station) {
|
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")
|
.appendingPathComponent("alert-map-\(UUID().uuidString).png")
|
||||||
if let data = image.pngData(), (try? data.write(to: url)) != nil,
|
if let data = image.pngData(), (try? data.write(to: url)) != nil,
|
||||||
let attachment = try? UNNotificationAttachment(identifier: "map", url: url, options: nil) {
|
let attachment = try? UNNotificationAttachment(identifier: "map", url: url, options: nil) {
|
||||||
finalContent.attachments = [attachment]
|
content.attachments = [attachment]
|
||||||
status = "map: attached"
|
status = "map: attached"
|
||||||
}
|
}
|
||||||
} else if let error {
|
} else if let error {
|
||||||
status = "map: snapshot failed (\(error.localizedDescription))"
|
status = "map: snapshot failed (\(error.localizedDescription))"
|
||||||
}
|
}
|
||||||
mapStatus?(status)
|
deliver(status)
|
||||||
// Replace the delivered notification (same identifier) so the
|
|
||||||
// map appears in place.
|
|
||||||
UNUserNotificationCenter.current().add(
|
|
||||||
UNNotificationRequest(identifier: identifier, content: finalContent, trigger: nil)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>logFormatVersion</key>
|
||||||
|
<integer>12</integer>
|
||||||
|
<key>logs</key>
|
||||||
|
<dict/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>logFormatVersion</key>
|
||||||
|
<integer>12</integer>
|
||||||
|
<key>logs</key>
|
||||||
|
<dict/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>logFormatVersion</key>
|
||||||
|
<integer>12</integer>
|
||||||
|
<key>logs</key>
|
||||||
|
<dict/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>logFormatVersion</key>
|
||||||
|
<integer>12</integer>
|
||||||
|
<key>logs</key>
|
||||||
|
<dict/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
Reference in New Issue
Block a user