iOS推送通知:如何在应用程序在后台运行时检测用户是否点击了通知?

146

虽然有很多关于这个主题的stackoverflow帖子,但我仍然没有找到一个好的解决方案。

如果应用程序不在后台,我可以在application:didFinishLaunchingWithOptions:调用中检查launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]以查看是否从通知打开它。

如果应用程序在后台,所有的帖子都建议使用application:didReceiveRemoteNotification:并检查应用程序状态。但是,正如我所实验过的(并且正如这个API的名称所暗示的那样),当接收到通知而不是被点击时,会调用此方法。

因此问题是,如果启动应用程序并将其移到后台,并且您知道从application:didReceiveNotification(在此时不会触发application:didFinishLaunchWithOptions:)接收到通知,则如何知道用户是通过点击通知恢复应用程序还是仅仅点击应用程序图标?因为如果用户点击了通知,则期望打开该通知中提到的页面,否则不应该。

我可以使用handleActionWithIdentifier来处理自定义操作通知,但这仅在用户单击自定义操作按钮时才会触发,而不是在用户单击通知的主体时触发。

谢谢。

编辑:

在阅读下面的一个答案后,我认为可以这样澄清我的问题:

我们如何区分这两种情况:

(A) 1.应用程序进入后台;2.收到通知;3.用户点击通知;4.应用程序进入前台

(B) 1.应用程序进入后台;2.收到通知;3.用户忽略通知并稍后点击应用程序图标;4.应用程序进入前台

由于application:didReceiveRemoteNotification:在步骤2中同时被触发,所以应该如何区分这两种情况。

或者,application:didReceiveRemoteNotification:只应在(A)步骤3中被触发,但我配置了应用程序出错,因此我在步骤2中看到它吗?


2
@soulshined 我确实阅读了文档,并且对 didReceiveRemoteNotification 的作用进行了学习。你真的读了我的问题吗?根据苹果官方文档,didReceiveRemoteNotification "告诉委托人正在运行的应用程序接收到远程通知"。我想知道如何判断用户是否点击了通知的好方法。你提到的 SO 链接是针对应用程序从头开始启动的情况,而我问的是应用程序在后台运行的情况。 - Bao Lei
我在第一条评论中回答了你的问题,并在链接中提供了一个示例。当您创建自定义负载时,可以在didFinishLaunching中检索其中的数据,然后根据需要采取行动,将用户发送到您认为合适的视图(这就是您在第一段中暗示的内容),这基本上是您如何知道他们是否通过通知打开了应用程序。A)您是正确的,因为它是在启动后发生的方法,这是您希望处理负载的位置,如果用户从通知中打开它。请阅读上面链接中的文档2.5。好文章。 - soulshined
从这里开始:在iOS中,委托对象在实现application:didFinishLaunchingWithOptions:方法时,使用UIApplicationLaunchOptionsRemoteNotificationKey键来访问启动选项字典中的有效负载。然后,它还有另一个链接到“通知有效负载”,以创建自己的字典。这是一个重复的问题,所以我没有回答它。 - soulshined
2
可能是重复的问题:[iOS / XCode:如何确定应用程序是通过点击通知还是Springboard应用程序图标启动的?](https://dev59.com/SGnWa4cB1Zd3GeqP3bdj) - soulshined
2
@soulshined 好的,也许我没有表述清楚。我的意思是如果应用程序完全退出,而不是在后台运行,则确实会调用didFinishLaunching。但是,如果您启动应用程序,然后将应用程序置于后台,现在通知进入,用户点击通知,现在didFinishLaunching不会再次被调用。相反,将调用applicationWillEnterForeground和applicationDidBecomeActive。如何判断应用程序是因为用户点击通知还是应用程序图标而进入前台呢? - Bao Lei
显示剩余8条评论
15个回答

114

好的,我终于弄清楚了。

在目标设置➝功能选项卡➝后台模式中,如果您勾选了“远程通知”,application:didReceiveRemoteNotification:将在通知到达时触发(只要应用程序在后台),在这种情况下无法确定用户是否会点击通知。

如果您取消选中该框,则仅在您点击通知时才会触发application:didReceiveRemoteNotification:

很奇怪勾选此框将更改应用程序委托方法之一的行为方式。如果勾选该框,则Apple使用两种不同的委托方法进行通知接收和通知点击。我认为大多数开发人员都希望知道是否单击了通知。

希望这对遇到此问题的其他人有所帮助。Apple 在这里也没有清楚地记录,因此我花了一段时间才找到答案。

enter image description here


10
我的 "背景模式" 已完全设置为 "关闭",但当应用程序处于后台模式时,仍然会收到通知。 - Gottfried
5
太好了!这对我很有帮助。然而遗憾的是我需要关闭远程通知。当推送通知到达并检测到用户点击时,我想预获取数据。我找不到实现这一点的方法。 - Peter Fennema
3
我将"背景模式"设置为“启用”并启用了远程通知,但仍然无法检测到通知事件。 - kalim sayyad
1
我有同样的问题,后台模式已设置为“开启”,并启用了远程通知,但仍然无法在后台模式下收到应用程序接收到的通知。 - Swapnil Dhotre
最低部署目标为iOS 10,因此不再需要这样做,因为新的“UserNotificationCenter”API不再受到这些烦人的问题的影响。 - fabb
显示剩余4条评论

79

我一直在寻找与你相同的东西,实际上找到了一种解决方案,不需要勾选远程通知。

要检查用户是否已点击,或应用程序处于后台还是活动状态,只需检查应用程序状态即可。

-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{

    if(application.applicationState == UIApplicationStateActive) {

        //app is currently active, can update badges count here

    }else if(application.applicationState == UIApplicationStateBackground){

        //app is in background, if content-available key of your notification is set to 1, poll to your backend to retrieve data and update your interface here

    }else if(application.applicationState == UIApplicationStateInactive){

        //app is transitioning from background to foreground (user taps notification), do what you need when user taps here

    }

更多信息请查看:

UIKit Framework参考 > UIApplication类参考 > UIApplicationState


11
如果用户在屏幕上激活应用程序,例如从顶部滑动打开通知中心或者从底部滑动打开控制中心,那么应用程序的状态可能会变为UIApplicationStateInactive。显然,无法确定APNS推送是在应用程序处于非活跃状态时接收到的还是用户实际点击了推送通知。请问您需要对此内容进行翻译吗? - Kostia Kim
1
@KostiaKim 你说得没错,但那是一个非常特殊的情况...除此之外,这个答案在区分前台应用程序、用户点击和后台应用程序方面是最有效的。 - mfaani
谢谢,这有点让一切变得清晰易懂了。请记住,在应用程序处于后台时调用委托方法,推送通知必须包含 content-available 键,但是通知必须是静默的(即不包括声音或标记),如官方文档所述。 - Nickkk
1
@KostiaKim,我实际上已经解决了不知道控制中心是否打开或用户是否实际点击通知的问题,通过添加一个布尔值,在func applicationDidEnterBackground(_ application: UIApplication)中设置为true,在func applicationDidBecomeActive(_ application: UIApplication)中设置为false,这使我能够在应用程序因控制中心或通知列表而处于非活动状态时显示应用内通知。 - DatForis
4
让我感到困惑的是,为什么苹果没有展示一个不同的事件或者简单地在元数据中添加一个标识来表明用户实际上与通知进行了交互。根据应用程序状态的环境信息“推断”用户是否采取了行动,对于影响用户对应用程序行为期望的重要信息来说,这相当不合理。也许唯一可做的就是创建一个自定义操作来打开带有该信息的应用程序? - Allan Nienhuis
显示剩余2条评论

21

1
在这种情况下它不会起作用。我之前试过了。当复选框被勾选时(详见我的已接受答案),当通知到达时,即使用户没有点击通知(通知只是到达了),你的带有注释“// user has tapped notification”的那行代码也会被执行。 - Bao Lei
4
我不同意。我已经在我的后台功能中勾选了"远程通知",并且它的工作方式与我回答中描述的一样。我也勾选了"后台抓取",也许这导致了变化? - Werner Kratochwil
你能给我的答案点个赞吗?;-)我还需要一些票才能更多地参与stackoverflow。非常感谢 - Werner Kratochwil
是的,这就是解决方案。谢谢。 - Afshin
我已经完美地实现了一切,也能够接收到通知。但是除非用户点击通知消息,否则它永远不会进入application:didReceiveRemoteNotification:方法。你有任何解决方案吗? - Moxarth
显示剩余3条评论

19
我也遇到了这个问题,但是在iOS 11上使用新的UserNotifications框架。
对于我来说,情况如下:
  • 新启动:application:didFinishLaunchingWithOptions:
  • 从终止状态接收:application(_:didReceiveRemoteNotification:fetchCompletionHandler:)
  • 前台接收:userNotificationCenter(_:willPresent:withCompletionHandler:)
  • 后台接收:userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:

这是在iOS 11+中完成它的方法。 - OhadM
6
这太复杂了。 - Zorayr

18

如果有人想要Swift 3.0版本的话

switch application.applicationState {
    case .active:
        //app is currently active, can update badges count here
        break
    case .inactive:
        //app is transitioning from background to foreground (user taps notification), do what you need when user taps here
        break
    case .background:
        //app is in background, if content-available key of your notification is set to 1, poll to your backend to retrieve data and update your interface here
        break
    default:
        break
    }

适用于 Swift 4

switch UIApplication.shared.applicationState {
case .active:
    //app is currently active, can update badges count here
    break
case .inactive:
    //app is transitioning from background to foreground (user taps notification), do what you need when user taps here
    break
case .background:
    //app is in background, if content-available key of your notification is set to 1, poll to your backend to retrieve data and update your interface here
    break
default:
    break
}

我们应该在哪个触发器中添加这段代码,didReceiveRemoteNotification还是didFinishLaunchingWithOptions? - Dashrath
2
在didReceiveRemoteNotification方法中 - Hamish
@Hamid sh,,,, 我在所有状态下都收到了推送通知,即当应用程序在前台、后台和关闭(终止)时..! 但我的问题是,当应用程序处于后台状态和关闭(终止)状态时如何增加徽章计数?请详细告诉我该怎么做....? 我的应用程序徽章计数仅在应用程序处于前台状态时增加.....? 如果可能,请简要告诉我....! - Kiran Jadhav

10
如果您将 "后台模式" > "远程通知" 选项打勾为是,那么点击通知事件会到达:
-(void)userNotificationCenter:(UNUserNotificationCenter *)center **didReceiveNotificationResponse**:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler.

它对我有所帮助,希望你也能喜欢 :)


我看到苹果公司添加了这个功能,但是无法弄清楚如何以这种方式获取通知的自定义有效负载。 - sudo
当应用程序处于后台状态并变为活动状态时,以及从已终止的状态启动应用程序时,将调用此函数。 - Nikhil Muskur
只有在启用“远程通知”时才会执行@NikhilMuskur,对吧? - Zorayr

10

PushNotificationManager类中,有两个函数处理接收到的推送通知:

class PushNotificationManager: NSObject, MessagingDelegate, UNUserNotificationCenterDelegate{

}

当我测试第一个触发器时,它会在通知到达后立即触发。

@available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
    
    completionHandler(UNNotificationPresentationOptions.alert)
    
    //OnReceive Notification
    let userInfo = notification.request.content.userInfo
    for key in userInfo.keys {
         Constants.setPrint("\(key): \(userInfo[key])")
    }
    
    completionHandler([])
    
}

并且第二个是当通知被点击时:

@available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
    
    //OnTap Notification
    let userInfo = response.notification.request.content.userInfo
    for key in userInfo.keys {
        Constants.setPrint("\(key): \(userInfo[key])")
    }
    
    completionHandler()
}

我还测试了远程通知的开启和关闭状态(在后台模式下)。


6

对于iOS 10及以上版本,在AppDelegate中添加以下代码,以了解通知是否被点击(即使应用程序关闭或打开也可以使用)

func userNotificationCenter(_ center: UNUserNotificationCenter,
                                didReceive response: UNNotificationResponse,
                                withCompletionHandler completionHandler: @escaping () -> Void) {
print("notification tapped here")
}

2
这是适用于iOS 10+的正确答案。在此线程中提供了完整的解释:https://dev59.com/9lkS5IYBdhLWcg3wdmgx#41783666。 - dead_can_dance

4
在我这种情况下,关闭后台模式没有任何区别。但是当应用程序被挂起并且用户点击通知时,我可以在这个回调方法中处理这种情况:
func userNotificationCenter(_ center: UNUserNotificationCenter,
                            didReceive response: UNNotificationResponse,
                            withCompletionHandler completionHandler: @escaping () -> Void) {

}

这是苹果官方的说法。根据苹果的文档,每当用户与推送通知UI交互时,它将被调用。如果应用程序不在后台,则会在后台模式下启动应用程序并调用此方法。 - Ryan

1

SWIFT 5.1

UIApplication.State 对我无效,因为一旦我在我的应用程序中读取指纹(模态显示),通知也会在上方栏中抛出,并且用户必须单击它。

我创建了

public static var isNotificationFromApp: Bool = false

AppDelegate 中,我在我的起始 viewController 中将其设置为 true,然后在我的通知 storyboard/viewController 中仅检查它 :)

希望它能派上用场


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接