iOS 推送通知:当应用程序处于后台时,如何检测用户是否点击了通知?
iOS push notification: how to detect if the user tapped on notification when the app is in background?
有很多关于这个主题的Whosebug线程,但我仍然没有找到好的解决方案。
如果应用程序不在后台,我可以在 application:didFinishLaunchingWithOptions:
调用中检查 launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]
以查看它是否从通知中打开。
如果应用程序在后台,所有帖子都建议使用 application:didReceiveRemoteNotification:
并检查应用程序状态。但是正如我所试验的那样(也正如这个 API 的名称所暗示的那样),这个方法在收到通知时被调用,而不是被点击。
所以问题是,如果应用程序启动然后后台运行,并且您知道收到来自 application:didReceiveNotification
的通知(此时 application:didFinishLaunchWithOptions:
不会触发),您如何知道用户是通过点击通知还是仅点击应用程序图标来恢复应用程序?因为如果用户点击通知,则期望打开该通知中提到的页面。否则不应该。
我可以将 handleActionWithIdentifier
用于自定义操作通知,但这只会在点击自定义操作按钮时触发,而不是在用户点击通知主体时触发。
谢谢。
编辑:
看完下面的一个回答,我想这样我就可以澄清我的问题了:
我们如何区分这两种情况:
(A) 1.app 进入后台; 2.notification收到; 3. 用户点击通知; 4. app进入前台
(B) 1.app 进入后台; 2.notification收到; 3. 用户忽略通知并稍后点击应用程序图标; 4. app进入前台
因为 application:didReceiveRemoteNotification:
在步骤 2 的两种情况下都被触发。
或者,application:didReceiveRemoteNotification:
是否应该仅在 (A) 的第 3 步中触发,但我以某种方式配置了我的应用程序错误,所以我在第 2 步看到它?
您可以将推送通知的有效负载配置为在应用程序处于后台时调用应用程序委托的 application:didReceiveRemoteNotification:fetchCompletionHandler:
方法。您可以在此处设置一些标志,以便用户下次启动您的应用程序时,您可以执行您的操作。
从 Apple 的文档中,您应该使用此方法下载与推送通知相关的新内容。同样要使其工作,您必须从后台模式启用远程通知,并且您的推送通知负载必须包含 content-available
键并将其值设置为 1。有关更多信息,请参阅使用推送通知启动苹果的下载部分文档 here.
另一种方法是在推送通知有效负载中计算徽章。因此,下次您的应用程序启动时,您可以检查应用程序徽章计数。如果它大于零,请执行您的操作并且 zero/clear 来自服务器的徽章计数。
希望对您有所帮助。
好吧,我终于明白了。
在目标设置➝功能选项卡➝后台模式中,如果您选中"Remote Notifications",application:didReceiveRemoteNotification:
将在通知到达时立即触发(只要应用程序在后台),在这种情况下,无法判断用户是否会点击通知。
如果取消选中该框,application:didReceiveRemoteNotification:
仅当您点击通知时才会触发。
有点奇怪,选中此框会更改其中一个应用程序委托方法的行为方式。如果选中该框会更好,Apple 使用两种不同的委托方法来接收通知和点击通知。我认为大多数开发人员总是想知道是否点击了通知。
希望这对 运行 关注此问题的其他人有所帮助。 Apple 也没有清楚地记录它 here 所以我花了一段时间才弄明白。
根据
iOS / XCode: how to know that app has been launched with a click on notification or on springboard app icon?
您必须像这样检查 didReceiveLocalNotification 中的应用程序状态:
if ([UIApplication sharedApplication].applicationState == UIApplicationStateInactive)
{
// user has tapped notification
}
else
{
// user opened app from app icon
}
虽然对我来说没有完全意义,但它似乎有效。
我一直在寻找与您相同的东西,实际上找到了一个不需要勾选远程通知的解决方案。
要检查用户是否点击,或应用程序是否在后台或处于活动状态,您只需要在
中检查应用程序状态
-(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 Reference > UIApplication Class Reference > UIApplicationState
如果有人想要 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
}
如果您 "Background Modes" > "Remote notifications" 选中 == 是,点击通知事件将到达:
-(void)userNotificationCenter:(UNUserNotificationCenter *)center **didReceiveNotificationResponse**:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler.
它帮助了我。请欣赏:)
我也 运行 遇到了这个问题 — 但在 iOS 11 上使用了新的 UserNotifications
框架。
这里对我来说是这样的:
- 新发布:
application:didFinishLaunchingWithOptions:
- 从终止状态收到:
application(_:didReceiveRemoteNotification:fetchCompletionHandler:)
- 在前台收到:
userNotificationCenter(_:willPresent:withCompletionHandler:)
- 在后台收到:
userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:
就我而言,关闭后台模式没有任何区别。然而,当应用程序被挂起并且用户点击通知时,我可以在这个回调方法中处理这个案例:
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
}
对于 iOS 10 及更高版本,将其放入 AppDelegate 中,以了解点击通知(即使应用程序关闭或打开也能正常工作)
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
print("notification tapped here")
}
PushNotificationManager
class:
里面有两个函数来处理接收到的PushNotification
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()
}
我还用远程通知的开启和关闭状态(在后台模式下)测试了它
SWIFT 5.1
UIApplication.State
对我不起作用,因为一旦我在我的应用程序中读取指纹(显示模态),通知也会在上部栏中抛出,用户必须单击它。
我创建了
public static var isNotificationFromApp: Bool = false
在 AppDelegate
中,我将它设置为 true
在我的开始 viewController
中,然后在我的通知中 storyboard
/viewController
我只是检查一下:)
希望能派上用场
经过大量试验和错误后,通过更改 Deployent 目标解决。
我在使用 iOS 部署目标 9.0 和 10.0、XCode 版本 11.6 时遇到了同样的问题(11E708)。 - 当应用程序处于 background.
时,没有通过点击通知获得任何委托方法调用
解决此问题的方法是将目标更改为 iOS 11.0。我终于开始在 UNUserNotificationCenter
委托 didReceive:
实现中从 冷启动 和 背景 场景中获取通知点击调用。
此外,willReceive:
现在也可以在 前景 场景中获得调用(正如预期的那样)。
我正在使用的负载如下。请注意,没有设置 content-available
或 mutable-content
。
{
"aps": {
"sound": "default",
"badge": 1,
"alert": {
"body": "test",
"title": "test"
}
}
}
如Aleks, we can use userNotificationCenter(_:didReceive:withCompletionHandler:)
(doc) 所述的方法来捕获用户对通知的响应。我们需要在 AppDelegate 的 application(_:didFinishLaunchingWithOptions:)
或 application(_:willFinishLaunchingWithOptions:)
方法中设置 UNUserNotificationCenter
的共享对象的委托。基本上我所做的是以下内容。
首先,
import UserNotifications
其次,使AppDelegate
符合协议UNUserNotificationCenterDelegate
@UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
// your app delegate code
}
三、在application(_:willFinishLaunchingWithOptions:)
中设置UNUserNotificationCenter
对象的delegate为AppDelegate
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
UNUserNotificationCenter.current().delegate = self
return true
}
四、实现委托方法userNotificationCenter(_:didReceive:withCompletionHandler:)
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
let application = UIApplication.shared
if response.actionIdentifier == UNNotificationDefaultActionIdentifier {
let userInfo = response.notification.request.content.userInfo
self.handlePushNotification(application, userInfo: userInfo)
}
}
最后,handlePushNotificationMethod
这是您的私有方法,您可以在其中实现处理通知的所有逻辑。
func handlePushNotification(userInfo: [AnyHashable : Any]) {
if let aps = userInfo["aps"] as? NSDictionary {
...
}
}
只要用户点击通知或用户对通知执行某种操作,就会调用此方法 (userNotificationCenter(_:didReceive:withCompletionHandler:)
)。就我而言,我只想点击通知。所以我将响应的 actionIdentifier 与默认操作标识符进行了比较。
实施时请注意 UserNotifications
框架的可用性。
有一种 'hacky' 方法可以知道在应用 运行(前台)或用户点击时是否收到了通知。
TLDR 版本:
从后台启动时,应用程序生命周期会经历 applicationWillEnterForeground
。因此,用户点击。
实施:
首先要过滤的事情(如多次所述)是应用程序是否处于非活动状态。
if application.applicationState == .inactive // You can ignore all the rest
如果明显是前景,背景状态将永远不会发生和激活。应用程序状态的其余问题是确实不活动(用户拉动控制中心)或由用户点击打开。
在这两种情况下,状态都是 inactive
.
但是,当用户点击通知时,应用程序首先经过 applicationWillEnterForeground
。简单地检查从 applicationWillEnterForeground
到 didReceiveRemoteNotification
的时间间隔应该可以解决这个问题。
// In AppDelegate
var applicationWillEnterForegroundTime = Date()
func applicationWillEnterForeground(_ application: UIApplication) {
applicationWillEnterForegroundTime = Date()
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
if application.applicationState == .inactive,
Date().timeIntervalSince(applicationWillEnterForegroundTime) < 1.0 {
// Push notification was opened by user tap
}
}
有很多关于这个主题的Whosebug线程,但我仍然没有找到好的解决方案。
如果应用程序不在后台,我可以在 application:didFinishLaunchingWithOptions:
调用中检查 launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]
以查看它是否从通知中打开。
如果应用程序在后台,所有帖子都建议使用 application:didReceiveRemoteNotification:
并检查应用程序状态。但是正如我所试验的那样(也正如这个 API 的名称所暗示的那样),这个方法在收到通知时被调用,而不是被点击。
所以问题是,如果应用程序启动然后后台运行,并且您知道收到来自 application:didReceiveNotification
的通知(此时 application:didFinishLaunchWithOptions:
不会触发),您如何知道用户是通过点击通知还是仅点击应用程序图标来恢复应用程序?因为如果用户点击通知,则期望打开该通知中提到的页面。否则不应该。
我可以将 handleActionWithIdentifier
用于自定义操作通知,但这只会在点击自定义操作按钮时触发,而不是在用户点击通知主体时触发。
谢谢。
编辑:
看完下面的一个回答,我想这样我就可以澄清我的问题了:
我们如何区分这两种情况:
(A) 1.app 进入后台; 2.notification收到; 3. 用户点击通知; 4. app进入前台
(B) 1.app 进入后台; 2.notification收到; 3. 用户忽略通知并稍后点击应用程序图标; 4. app进入前台
因为 application:didReceiveRemoteNotification:
在步骤 2 的两种情况下都被触发。
或者,application:didReceiveRemoteNotification:
是否应该仅在 (A) 的第 3 步中触发,但我以某种方式配置了我的应用程序错误,所以我在第 2 步看到它?
您可以将推送通知的有效负载配置为在应用程序处于后台时调用应用程序委托的 application:didReceiveRemoteNotification:fetchCompletionHandler:
方法。您可以在此处设置一些标志,以便用户下次启动您的应用程序时,您可以执行您的操作。
从 Apple 的文档中,您应该使用此方法下载与推送通知相关的新内容。同样要使其工作,您必须从后台模式启用远程通知,并且您的推送通知负载必须包含 content-available
键并将其值设置为 1。有关更多信息,请参阅使用推送通知启动苹果的下载部分文档 here.
另一种方法是在推送通知有效负载中计算徽章。因此,下次您的应用程序启动时,您可以检查应用程序徽章计数。如果它大于零,请执行您的操作并且 zero/clear 来自服务器的徽章计数。
希望对您有所帮助。
好吧,我终于明白了。
在目标设置➝功能选项卡➝后台模式中,如果您选中"Remote Notifications",application:didReceiveRemoteNotification:
将在通知到达时立即触发(只要应用程序在后台),在这种情况下,无法判断用户是否会点击通知。
如果取消选中该框,application:didReceiveRemoteNotification:
仅当您点击通知时才会触发。
有点奇怪,选中此框会更改其中一个应用程序委托方法的行为方式。如果选中该框会更好,Apple 使用两种不同的委托方法来接收通知和点击通知。我认为大多数开发人员总是想知道是否点击了通知。
希望这对 运行 关注此问题的其他人有所帮助。 Apple 也没有清楚地记录它 here 所以我花了一段时间才弄明白。
根据 iOS / XCode: how to know that app has been launched with a click on notification or on springboard app icon? 您必须像这样检查 didReceiveLocalNotification 中的应用程序状态:
if ([UIApplication sharedApplication].applicationState == UIApplicationStateInactive)
{
// user has tapped notification
}
else
{
// user opened app from app icon
}
虽然对我来说没有完全意义,但它似乎有效。
我一直在寻找与您相同的东西,实际上找到了一个不需要勾选远程通知的解决方案。
要检查用户是否点击,或应用程序是否在后台或处于活动状态,您只需要在
中检查应用程序状态-(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 Reference > UIApplication Class Reference > UIApplicationState
如果有人想要 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
}
如果您 "Background Modes" > "Remote notifications" 选中 == 是,点击通知事件将到达:
-(void)userNotificationCenter:(UNUserNotificationCenter *)center **didReceiveNotificationResponse**:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler.
它帮助了我。请欣赏:)
我也 运行 遇到了这个问题 — 但在 iOS 11 上使用了新的 UserNotifications
框架。
这里对我来说是这样的:
- 新发布:
application:didFinishLaunchingWithOptions:
- 从终止状态收到:
application(_:didReceiveRemoteNotification:fetchCompletionHandler:)
- 在前台收到:
userNotificationCenter(_:willPresent:withCompletionHandler:)
- 在后台收到:
userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:
就我而言,关闭后台模式没有任何区别。然而,当应用程序被挂起并且用户点击通知时,我可以在这个回调方法中处理这个案例:
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
}
对于 iOS 10 及更高版本,将其放入 AppDelegate 中,以了解点击通知(即使应用程序关闭或打开也能正常工作)
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
print("notification tapped here")
}
PushNotificationManager
class:
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()
}
我还用远程通知的开启和关闭状态(在后台模式下)测试了它
SWIFT 5.1
UIApplication.State
对我不起作用,因为一旦我在我的应用程序中读取指纹(显示模态),通知也会在上部栏中抛出,用户必须单击它。
我创建了
public static var isNotificationFromApp: Bool = false
在 AppDelegate
中,我将它设置为 true
在我的开始 viewController
中,然后在我的通知中 storyboard
/viewController
我只是检查一下:)
希望能派上用场
经过大量试验和错误后,通过更改 Deployent 目标解决。
我在使用 iOS 部署目标 9.0 和 10.0、XCode 版本 11.6 时遇到了同样的问题(11E708)。 - 当应用程序处于 background.
时,没有通过点击通知获得任何委托方法调用解决此问题的方法是将目标更改为 iOS 11.0。我终于开始在 UNUserNotificationCenter
委托 didReceive:
实现中从 冷启动 和 背景 场景中获取通知点击调用。
此外,willReceive:
现在也可以在 前景 场景中获得调用(正如预期的那样)。
我正在使用的负载如下。请注意,没有设置 content-available
或 mutable-content
。
{
"aps": {
"sound": "default",
"badge": 1,
"alert": {
"body": "test",
"title": "test"
}
}
}
如Aleks, we can use userNotificationCenter(_:didReceive:withCompletionHandler:)
(doc) 所述的方法来捕获用户对通知的响应。我们需要在 AppDelegate 的 application(_:didFinishLaunchingWithOptions:)
或 application(_:willFinishLaunchingWithOptions:)
方法中设置 UNUserNotificationCenter
的共享对象的委托。基本上我所做的是以下内容。
首先,
import UserNotifications
其次,使AppDelegate
符合协议UNUserNotificationCenterDelegate
@UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
// your app delegate code
}
三、在application(_:willFinishLaunchingWithOptions:)
UNUserNotificationCenter
对象的delegate为AppDelegate
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
UNUserNotificationCenter.current().delegate = self
return true
}
四、实现委托方法userNotificationCenter(_:didReceive:withCompletionHandler:)
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
let application = UIApplication.shared
if response.actionIdentifier == UNNotificationDefaultActionIdentifier {
let userInfo = response.notification.request.content.userInfo
self.handlePushNotification(application, userInfo: userInfo)
}
}
最后,handlePushNotificationMethod
这是您的私有方法,您可以在其中实现处理通知的所有逻辑。
func handlePushNotification(userInfo: [AnyHashable : Any]) {
if let aps = userInfo["aps"] as? NSDictionary {
...
}
}
只要用户点击通知或用户对通知执行某种操作,就会调用此方法 (userNotificationCenter(_:didReceive:withCompletionHandler:)
)。就我而言,我只想点击通知。所以我将响应的 actionIdentifier 与默认操作标识符进行了比较。
实施时请注意 UserNotifications
框架的可用性。
有一种 'hacky' 方法可以知道在应用 运行(前台)或用户点击时是否收到了通知。
TLDR 版本:
从后台启动时,应用程序生命周期会经历 applicationWillEnterForeground
。因此,用户点击。
实施:
首先要过滤的事情(如多次所述)是应用程序是否处于非活动状态。
if application.applicationState == .inactive // You can ignore all the rest
如果明显是前景,背景状态将永远不会发生和激活。应用程序状态的其余问题是确实不活动(用户拉动控制中心)或由用户点击打开。
在这两种情况下,状态都是 inactive
.
但是,当用户点击通知时,应用程序首先经过 applicationWillEnterForeground
。简单地检查从 applicationWillEnterForeground
到 didReceiveRemoteNotification
的时间间隔应该可以解决这个问题。
// In AppDelegate
var applicationWillEnterForegroundTime = Date()
func applicationWillEnterForeground(_ application: UIApplication) {
applicationWillEnterForegroundTime = Date()
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
if application.applicationState == .inactive,
Date().timeIntervalSince(applicationWillEnterForegroundTime) < 1.0 {
// Push notification was opened by user tap
}
}