当您的应用程序处于打开和前台时,如何显示一个股票iOS通知横幅?

136

当Apple的官方iOS消息应用程序处于打开并在前台时,来自其他联系人的新消息会触发一个股票、原生的iOS通知警报横幅。如下图所示。

第三方应用在App Store上是否可以实现此功能?当您的应用程序处于打开并在前台时,是否可以进行本地和/或推送通知?

在测试我的应用程序时,可以收到通知,但没有显示iOS警报UI

但是,在Apple的官方消息应用程序中,可以看到这种行为:

Messages is open and in the foreground. Still shows a notification alert.

本地和远程通知编程指南中说:

当操作系统传递本地通知或远程通知且目标应用程序未在前台运行时,它可以通过警报、图标徽章号码或声音向用户呈现通知。

如果应用程序在前台运行时传递通知,则应用程序委托将接收到本地或远程通知。

因此,在前台时,我们可以接收有关通知数据。但我看不到任何方法来显示原生的iOS通知警报UI

-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo 
{
    // I know we still receive the notification `userInfo` payload in the foreground.
    // This question is about displaying the stock iOS notification alert UI.

    // Yes, one *could* use a 3rd party toast alert framework. 
    [self use3rdPartyToastAlertFrameworkFromGithub]
}

那么 Messages 使用私有 API 在前台显示警报吗?

为了这个问题,请不要建议在 GitHub 或其他地方使用任何第三方“toast”弹出警报。我只想知道是否可以使用 原生 iOS 的本地或推送通知警报 UI 在您的应用程序打开并在前台运行时

11个回答

179

iOS 10 增加了 UNUserNotificationCenterDelegate 协议,用于处理应用在前台时的通知。

UNUserNotificationCenterDelegate 协议定义了接收通知和处理操作的方法。当您的应用程序在前台时,到达的通知将传递到您的代理对象,而不是使用系统界面自动显示。

Swift:

optional func userNotificationCenter(_ center: UNUserNotificationCenter, 
                     willPresent notification: UNNotification, 
      withCompletionHandler completionHandler: (UNNotificationPresentationOptions) -> Void)

Objective-C:

客观-C。
- (void)userNotificationCenter:(UNUserNotificationCenter *)center 
       willPresentNotification:(UNNotification *)notification 
         withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler;
UNNotificationPresentationOptions 标志允许您指定 UNNotificationPresentationOptionAlert 以使用通知提供的文本显示警报。

这对于允许您在应用程序打开并处于前台时显示警报至关重要,这是 iOS 10 的新功能。


示例代码:

class AppDelegate: UIResponder, UIApplicationDelegate {
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
        // Set UNUserNotificationCenterDelegate
        UNUserNotificationCenter.current().delegate = self
        
        return true
    }
    
}

// Conform to UNUserNotificationCenterDelegate
extension AppDelegate: UNUserNotificationCenterDelegate {
    
    func userNotificationCenter(_ center: UNUserNotificationCenter,
           willPresent notification: UNNotification,
           withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void)
    {
        completionHandler(.alert)
    }
    
}

4
可以提供一个工作代码示例吗?使得当应用程序处于后台时,系统会像前台通知一样显示传入的前台通知。 - azmeuk
9
不要忘记在 application(_:willFinishLaunchingWithOptions:)application(_:didFinishLaunchingWithOptions:) 方法中添加 UNUserNotificationCenter.current().delegate = self - Deniss Fedotovs
4
请确保您的设备未开启请勿打扰模式,否则通知将显示在通知中心中,但不会在应用程序前景中呈现。请注意不要改变原文意思。 - Jadent
2
我的两分钱:不要错过:导入UserNotifications. ! - ingconti
1
.alert在iOS 14中已被弃用。我们应该用什么来替代它? - bze12
显示剩余11条评论

102

若要在应用程序前台显示横幅消息,请使用以下方法。

iOS 10+,Swift 3+:

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

1
哇,无论我在哪里阅读,人们都说这是不可能的。我很高兴找到了这个答案,它完全按照我的期望工作!当应用程序正在运行时,推送通知现在显示得就像应用程序在后台一样。谢谢! - Torre Lasley
1
这段代码应该放在哪里? 是在 appDelegate 中,还是其他地方? - Yoav R.
我使用了相同的代码。但是我不知道为什么当应用程序在前台时通知没有显示。 - Boominadha Prakash M
4
成三,你可能需要纠正这个问题:它不一定要放在 AppDelegate 中。它必须放在符合 UNUserNotificationCenterDelegate 协议的任何对象中。话虽如此,大多数开发人员都让他们的 AppDelegate 符合 UNUserNotificationCenterDelegate 协议。@YoavR. - mfaani
1
这个可以加条件吗?我的意思是,假设我正在显示聊天通知。如果聊天屏幕已经打开,我能不能不显示通知,否则就显示呢? - iOSer
显示剩余8条评论

16

编辑:

iOS 10现在可以实现前台通知警报!请参阅此答案

对于iOS 9及更低版本:

当您的应用程序处于打开并在前台时,似乎不可能显示iOS通知警报。 Messages.app可能正在使用私有API。

当应用程序已经在前端时,系统不会显示任何警报、标记应用程序图标或播放任何声音。- UILocalNotification文档

UIApplicationDelegate方法仍然会被调用,允许您的应用程序响应本地或远程通知:

application:didReceiveLocalNotification:
application:didReceiveRemoteNotification:

然而,与 Apple 的 Messages.app 不同,原生的 iOS 通知警报横幅界面将不会显示,这可能是因为它正在使用私有 API。

最好的方法是自己制作警报横幅或使用现有的框架:

-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo 
{    
    // Use a 3rd party toast alert framework to display a banner 
    [self toastAlertFromGithub]
}

我已经在这里为这种行为打开了一个雷达:rdar://22313177


3
那么WhatsApp和其他知名应用是如何做到这一点的呢? - Machado
@tardoandre 有截图吗?我猜这是通过在他们自己的视图中模仿股票iOS警报来实现的。 - pkamb
3
如果您建议使用这个第三方库,应该包含 toastAlertFromGithub 的链接! - Ketan Parmar

14

为了在应用程序打开时显示通知,我们需要手动处理它。因此,我在下面所做的是一旦收到通知就立即处理它。

在AppDelegate.m中添加以下所有内容:

  1. 处理通知调用
  2. 创建一个视图,添加AppIcon、通知消息,并将其显示为动画
  3. 添加“触摸识别器”以便在被触摸时或者在5秒钟内使用动画删除它

如果这是一个可行的解决方案,请让我知道。对我来说效果很好,但我不确定这是否是正确的方法。

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


NSString *notifMessage = [[userInfo objectForKey:@"aps"] objectForKey:@"alert"];

//Define notifView as UIView in the header file
[_notifView removeFromSuperview]; //If already existing

_notifView = [[UIView alloc] initWithFrame:CGRectMake(0, -70, self.window.frame.size.width, 80)];
[_notifView setBackgroundColor:[UIColor blackColor]];

UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(10,15,30,30)];
imageView.image = [UIImage imageNamed:@"AppLogo.png"];

UILabel *myLabel = [[UILabel alloc] initWithFrame:CGRectMake(60, 15, self.window.frame.size.width - 100 , 30)];
myLabel.font = [UIFont fontWithName:@"Helvetica" size:10.0];
myLabel.text = notifMessage;

[myLabel setTextColor:[UIColor whiteColor]];
[myLabel setNumberOfLines:0];

[_notifView setAlpha:0.95];

//The Icon
[_notifView addSubview:imageView];

//The Text
[_notifView addSubview:myLabel];

//The View
[self.window addSubview:_notifView];

UITapGestureRecognizer *tapToDismissNotif = [[UITapGestureRecognizer alloc] initWithTarget:self
                                                                                    action:@selector(dismissNotifFromScreen)];
tapToDismissNotif.numberOfTapsRequired = 1;
tapToDismissNotif.numberOfTouchesRequired = 1;

[_notifView addGestureRecognizer:tapToDismissNotif];


[UIView animateWithDuration:1.0 delay:.1 usingSpringWithDamping:0.5 initialSpringVelocity:0.1 options:UIViewAnimationOptionCurveEaseIn animations:^{

    [_notifView setFrame:CGRectMake(0, 0, self.window.frame.size.width, 60)];

} completion:^(BOOL finished) {


}];


//Remove from top view after 5 seconds
[self performSelector:@selector(dismissNotifFromScreen) withObject:nil afterDelay:5.0];


return;


}

//If the user touches the view or to remove from view after 5 seconds
- (void)dismissNotifFromScreen{

[UIView animateWithDuration:1.0 delay:.1 usingSpringWithDamping:0.5 initialSpringVelocity:0.1 options:UIViewAnimationOptionCurveEaseIn animations:^{

    [_notifView setFrame:CGRectMake(0, -70, self.window.frame.size.width, 60)];

} completion:^(BOOL finished) {


}];


}

3
这可能是创建自己的警报的好答案,但它并没有像截图中显示的那样显示股票 iOS警报。 - pkamb
谢谢您的赞美之词。是的,如果您在谈论外观,它不会像那样(指上面显示的iOS通知),因为通过代码创建的通知是自定义的。我们可以尝试在视图中复制外观。我认为WhatsApp有一个看起来很整洁的通知,带有背景模糊等效果。 - Hafeez
1
有人点了个踩的 :(. 能说一下为什么吗?谢谢。 - Hafeez
3
我了解,虽然这个答案很棒,但它并没有真正回答所提出的问题:如何在应用程序内显示原生iOS弹出通知。在其他一些Stack Overflow的问题中,这个答案可能非常有用。 - pkamb
2
感谢pkamb的澄清,很有道理。我想我漏掉了“股票”这个词。 - Hafeez
iPhone X的解决方法:将此代码段放入动画块中: if (UIApplication.sharedApplication.keyWindow.safeAreaInsets.top > 0.0) { [_notifView setFrame:CGRectMake(0, 30, self.window.frame.size.width, 60)]; } else { [_notifView setFrame:CGRectMake(0, 0, self.window.frame.size.width, 60)]; } } else { [_notifView setFrame:CGRectMake(0, 0, self.window.frame.size.width, 60)]; }``` - Lunarchaos42

10
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
content.body = body;
content.userInfo = userInfo;
content.sound = [UNNotificationSound defaultSound];
[content setValue:@(YES) forKeyPath:@"shouldAlwaysAlertWhileAppIsForeground"];

UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:@"Notif" content:content trigger:nil];
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
    DLog(@"Error:%@", error);
}];

我可以在iOS 10应用程序处于活动状态时显示推送通知。

  1. 来自服务器的推送通知应该是静默的

  2. 当接收到来自服务器的远程通知时,您发送本地通知并设置keyPath: shouldAlwaysAlertWhileAppIsForeground = True的值。


在 Swift 2 中,应该是 content.setValue("YES", forKeyPath: "shouldAlwaysAlertWhileAppIsForeground"),对吗?(使用 "Yes" 而不是 true) - Yoav R.
注意:这将在iOS 12下导致您的应用程序崩溃。请参见此处的讨论:https://dev59.com/s53ha4cB1Zd3GeqPY7tw - Matt Bearson

10

以下是在 iOS 10 和 Swift 2.3 中实现前台或打开应用时接收推送通知的代码:

@available(iOS 10.0, *)
func userNotificationCenter(center: UNUserNotificationCenter, willPresentNotification notification: UNNotification, withCompletionHandler completionHandler: (UNNotificationPresentationOptions) -> Void)
{
     completionHandler([UNNotificationPresentationOptions.Alert,UNNotificationPresentationOptions.Sound,UNNotificationPresentationOptions.Badge])
}

如果您需要访问通知的userInfo,请使用以下代码:notification.request.content.userInfo

只有在将属性content-available: 1添加到有效负载中时,才会调用userNotificationCenter(:willPresent:withCompletionHandler:)方法。最终有效负载应类似于:

{
     "aps":{
          "alert":"Testing.. (7)",
          "badge":1,"sound":"default"
     },
     "content-available":1
}

2
当您的应用在前台运行时,userNotificationCenter(_:willPresent:withCompletionHandler:)会被调用。因此,这与“content-available”无关,该内容与“后台获取”有关。 - DawnSong

6
你可以自己处理通知并显示自定义警报。类似 Viber、Whatsapp 和 BisPhone 的应用程序采用了这种方法。
第三方自定义警报的一个示例是 CRToast
尝试在应用程序前台安排本地通知,你会发现没有出现iOS原生警报:
if (application.applicationState == UIApplicationStateActive ) {
     UILocalNotification *localNotification = [[UILocalNotification alloc] init];
    localNotification.userInfo = userInfo;
    localNotification.soundName = UILocalNotificationDefaultSoundName;
    localNotification.alertBody = message;
    localNotification.fireDate = [NSDate date];
    [[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
}

谢谢,但我只是想找使用iOS系统自带的通知警告的答案。 - pkamb
1
好的,但我认为你做不到,还要看这个链接:https://dev59.com/RWUp5IYBdhLWcg3w_rvT - Mo Farhand
6
@matt,我试图保持这个问题的主题:在前台显示iOS股票警报。还有很多其他“最佳烤面包框架”问题。回答“不,您无法在前台显示iOS警报”是完全可以接受的,我会接受这个答案,直到在将来的iOS版本中可以实现为止。 - pkamb

4

Swift 3 版本

当应用程序在前台时,将显示一个警报。

if #available(iOS 10.0, *)
{
    // need to setup the global notification delegate somewhere when your app starts
    //
    UNUserNotificationCenter.current().delegate = applicationDelegate

    // to show a message
    //
    let content = UNMutableNotificationContent()
    content.body = "MESSAGE"

    let request = UNNotificationRequest(identifier: "fred", content: content, trigger: nil)
    UNUserNotificationCenter.current().add(request)
    {
       error in // called when message has been sent

       debugPrint("Error: \(error)")
    }
}

ApplicationDelegate的实现代码,关于UNUserNotificationCenterDelegate

@available(iOS 10.0, *)
public func userNotificationCenter(_ center : UNUserNotificationCenter, willPresent notification : UNNotification, withCompletionHandler completionHandler : @escaping (UNNotificationPresentationOptions) -> Void)
{
    completionHandler([.alert]) // only-always show the alert
}

你是说自从iOS 10以来,现在也可以像在后台一样在前台显示警报/横幅/声音了吗? - mfaani
在 did receiveRemoteNotification 方法中,我应该把上面的代码部分放在哪里? - user1960169
@Leslie Godwin,这个能在iOS 8.0上用于前台显示通知吗? - Dilip Tiwari

2
展示本地通知,这是最好的选择。只需少量代码即可编写“BRYXBanner”https://cocoapods.org/pods/BRYXBanner
let banner = Banner(title: "title", subtitle: "subtitle", image: UIImage(named: "addContact"), backgroundColor: UIColor(red:137.0/255.0, green:172.0/255.0, blue:2.0/255.0, alpha:1.000))
    banner.dismissesOnTap = true
    banner.show(duration: 1.0)

这个 BRYXBanner 框架似乎没有使用 原生 的 iOS 通知横幅。 - pkamb

2
这是一个与SwiftUI兼容的版本。请按以下方式修改您的主App文件:
import SwiftUI
import UserNotifications

let appDelegate = AppDelegate()

@main
struct MyApp: App {
    let persistenceController = PersistenceController.shared
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .onAppear() {
                    UNUserNotificationCenter.current().delegate = appDelegate
                }
                .environment(\.managedObjectContext, persistenceController.container.viewContext)
        }
    }
}

class AppDelegate: UIResponder, UIApplicationDelegate {
    
}

// Conform to UNUserNotificationCenterDelegate
extension AppDelegate: UNUserNotificationCenterDelegate {
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                willPresent notification: UNNotification,
                                withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void)
    {
        completionHandler([.alert, .badge, .sound])
    }
}

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