You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Notification Service Extension中检测SwiftUI应用是否处于后台状态?

在iOS Notification Service Extension中检测SwiftUI应用的运行状态

这个问题我之前也碰到过,核心原因是Notification Service Extension作为独立的扩展进程,和主App不在同一个运行空间里,所以根本没法直接访问主App的UIApplication实例——这就是为什么UIApplication.shared会报错的原因。下面给你几个可行的解决方案,按推荐优先级排序:

方案一:让前台推送直接跳过Extension调用(最可靠)

其实默认情况下,如果主App在前台时处理了推送且选择不显示通知,Notification Service Extension是不会被触发的。你可以检查下主App的推送代理实现是否正确:

步骤1:设置推送代理(SwiftUI App生命周期)

import SwiftUI
import UserNotifications

class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // 设置推送中心的代理为当前AppDelegate
        UNUserNotificationCenter.current().delegate = self
        return true
    }
    
    // 前台收到推送时的处理方法
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        // 选择不显示通知,这样Extension就不会被调用
        completionHandler(.none)
        
        // 如果你需要在前台处理推送逻辑,在这里写就行,不用走Extension
        PushNotificationHelper.handleNotification(userInfo: notification.request.content.userInfo, context: persistentContainer.viewContext)
    }
}

@main
struct YourApp: App {
    // 适配UIApplicationDelegate
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

这样设置后,只要主App在前台,推送会直接走到willPresent方法,Extension完全不会被触发,完美符合你的需求。

方案二:通过Payload标记区分前台/后台推送

如果你的业务需求必须让Extension在前台时也被调用,但需要跳过处理逻辑,可以让服务器在推送时根据主App的状态(比如主App可以主动上报自己的状态给服务器)添加一个标记字段,比如isForeground:

Extension中的处理代码

override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
    self.contentHandler = contentHandler
    bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
    
    guard let bestAttemptContent = bestAttemptContent else {
        contentHandler(request.content)
        return
    }
    
    // 读取推送Payload中的前台标记
    let userInfo = request.content.userInfo
    if let isAppForeground = userInfo["isForeground"] as? Bool, isAppForeground {
        // 应用处于前台,直接返回原通知内容,不执行处理逻辑
        contentHandler(bestAttemptContent)
        return
    }
    
    // 后台状态下执行你的处理逻辑
    PushNotificationHelper.handleNotification(userInfo: userInfo, context: persistentContainer.viewContext)
    contentHandler(bestAttemptContent)
}

这种方法的好处是不需要额外的状态同步,完全依赖推送Payload的标记,准确性很高,但需要服务器配合。

方案三:通过App Groups共享应用状态

如果不想依赖服务器,可以利用iOS的App Groups机制,让主App把自己的运行状态同步到共享存储空间,Extension再读取这个状态:

步骤1:配置App Groups

在Xcode中,分别给主App和Notification Service Extension添加同一个App Group(比如group.com.yourcompany.yourapp),具体操作是:

  • 选中你的项目 -> 选择主App Target -> 切换到Signing & Capabilities标签 -> 点击+ Capability -> 添加App Groups -> 新建或选择已有的Group
  • 重复上述操作给Extension Target添加同一个App Group

步骤2:主App同步状态到共享存储

import SwiftUI

@main
struct YourApp: App {
    @Environment(\.scenePhase) private var scenePhase
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .onChange(of: scenePhase) { newPhase in
            // 获取共享的UserDefaults
            guard let sharedDefaults = UserDefaults(suiteName: "group.com.yourcompany.yourapp") else { return }
            
            // 根据场景状态更新标记
            switch newPhase {
            case .active:
                sharedDefaults.set(true, forKey: "isAppForeground")
            case .inactive, .background:
                sharedDefaults.set(false, forKey: "isAppForeground")
            @unknown default:
                sharedDefaults.set(false, forKey: "isAppForeground")
            }
            sharedDefaults.synchronize()
        }
    }
}

步骤3:Extension读取共享状态

override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
    self.contentHandler = contentHandler
    bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
    
    guard let bestAttemptContent = bestAttemptContent else {
        contentHandler(request.content)
        return
    }
    
    // 读取共享存储中的应用状态
    guard let sharedDefaults = UserDefaults(suiteName: "group.com.yourcompany.yourapp") else {
        contentHandler(bestAttemptContent)
        return
    }
    let isAppForeground = sharedDefaults.bool(forKey: "isAppForeground")
    
    if isAppForeground {
        // 前台状态直接返回,不处理
        contentHandler(bestAttemptContent)
        return
    }
    
    // 后台状态执行处理逻辑
    PushNotificationHelper.handleNotification(userInfo: request.content.userInfo, context: persistentContainer.viewContext)
    contentHandler(bestAttemptContent)
}

这种方法的缺点是状态同步可能有轻微延迟,但大部分场景下足够使用。

内容的提问来源于stack exchange,提问作者alionthego

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.01 01:42:40