Skip to content

iOS: foreground notifications silently dropped — UNUserNotificationCenterDelegate never set #90

@pvg13

Description

@pvg13

Problem

wavesync_show_notification (in Sources/WaveSyncPush/WaveSyncPushBridge.swift) calls UNUserNotificationCenter.current().add(request) to display local notifications. However, iOS silently drops notification banners when the app is in the foreground unless a UNUserNotificationCenterDelegate is set and its userNotificationCenter(_:willPresent:withCompletionHandler:) method opts into banner display.

Neither WaveSyncPushBridge.swift nor WaveSyncAppDelegateProxy.m ever assigns UNUserNotificationCenter.current().delegate, so sync notifications (e.g. "Ana added milk") posted while the user has the app open are queued in the notification center but never shown as banners. The user sees nothing.

Minimal fix

In WaveSyncPushBridge.swift, add a lightweight delegate and install it inside wavesync_show_notification (dispatched to main thread, no-op if the host app already set its own delegate):

private final class _WaveSyncNotificationDelegate: NSObject, UNUserNotificationCenterDelegate {
    static let shared = _WaveSyncNotificationDelegate()

    func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        willPresent notification: UNNotification,
        withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
    ) {
        completionHandler([.banner, .sound])
    }
}

@_cdecl("wavesync_show_notification")
public func wavesync_show_notification(
    _ titlePtr: UnsafePointer<CChar>?,
    _ bodyPtr: UnsafePointer<CChar>?,
    _ groupPtr: UnsafePointer<CChar>?
) {
    // ... existing guard/CString unwrapping ...

    let center = UNUserNotificationCenter.current()
    // Ensure foreground banners are shown. Must run on main thread; no-op if
    // the host already set its own delegate.
    DispatchQueue.main.async {
        if center.delegate == nil {
            center.delegate = _WaveSyncNotificationDelegate.shared
        }
    }

    // ... rest of existing requestAuthorization + add(request) code ...
}

Alternatively, install the delegate in WaveSyncAppDelegateProxy.m's install_delegate_methods (which already runs on the main thread at UIApplicationDidFinishLaunchingNotification) so it is set as early as possible and the first notification is never missed.

Context

  • iOS 18+ suppresses foreground UNNotificationRequest banners by default. trigger: nil (immediate delivery) still delivers to the notification center, but the banner only appears if willPresent returns .banner.
  • Reproducer: open the app, have a second device add a household grocery item; the receiving device shows nothing despite the sync arriving successfully.
  • aps-environment / remote push is not involved — this is a local UNNotificationRequest.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions