From d418d184367d1233324cb0483bb0e7161c8ac5d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bj=C3=B6rkert?= Date: Tue, 12 May 2026 16:34:44 +0200 Subject: [PATCH] Suppress silent passive notifications in willPresent handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UNUserNotificationCenterDelegate.willPresent returned [.banner, .sound, .badge] unconditionally, which meant any notification iOS routed through this handler while the app was foregrounded produced sound — including the Live Activity push-to-start payload, which is intentionally silent (interruption-level: passive, empty title/body). Now returns [] for passive notifications and for ones with empty title/body. The four intentional alerts (renewal-failed, APNs credentials missing, push-to-start token missing, alarms) all use non-empty title/body and the default .active interruption level, so they continue to surface. Also expanded the willPresent log line with interruptionLevel and title/body presence so future reports can confirm whether iOS routed a given payload here. --- LoopFollow/Application/AppDelegate.swift | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/LoopFollow/Application/AppDelegate.swift b/LoopFollow/Application/AppDelegate.swift index 831395f02..f09e7b297 100644 --- a/LoopFollow/Application/AppDelegate.swift +++ b/LoopFollow/Application/AppDelegate.swift @@ -231,12 +231,21 @@ extension AppDelegate: UNUserNotificationCenterDelegate { willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { - // Log the notification - let userInfo = notification.request.content.userInfo - let userInfoKeys = userInfo.keys.compactMap { $0 as? String }.sorted() - LogManager.shared.log(category: .general, message: "Will present notification: keys=\(userInfoKeys)") + let content = notification.request.content + let userInfoKeys = content.userInfo.keys.compactMap { $0 as? String }.sorted() + LogManager.shared.log( + category: .general, + message: "Will present notification: keys=\(userInfoKeys), interruption=\(content.interruptionLevel.rawValue), title=\(content.title.isEmpty ? "empty" : "set"), body=\(content.body.isEmpty ? "empty" : "set")" + ) + + // Suppress notifications iOS routes here that we never intended to surface: + // the Live Activity push-to-start uses interruption-level: passive with empty + // title/body and must not produce a banner or sound when LF is foregrounded. + if content.interruptionLevel == .passive || (content.title.isEmpty && content.body.isEmpty) { + completionHandler([]) + return + } - // Show the notification even when app is in foreground completionHandler([.banner, .sound, .badge]) } }