在自己的应用程序/视图中接收本地通知(或如何在 SwiftUI 中注册 UNUserNotificationCenterDelegate )
receive local notifications within own app / view (or how to register a UNUserNotificationCenterDelegate in SwiftUI )
我正在使用包含倒计时功能的 SwiftUI 为 iOS 重新开发一个 android 应用程序。当倒计时结束时,应该通知用户倒计时结束。 Notification 应该具有一定的侵入性,并且适用于不同的场景,例如当用户未主动使用 phone 时,当用户正在使用我的应用程序时以及当用户正在使用其他应用程序时。我决定使用本地通知来实现这一点,这是 android 的工作方法。 (如果这种方法完全错误,请告诉我什么是最佳实践)
但是,当用户当前正在使用我的应用程序时,我无法收到通知。通知只显示在消息中心(所有通知排队的地方),但不会主动弹出。
到目前为止,这是我的代码:
要求用户获得在我的 CountdownOrTimerSheet 结构中使用通知的权限(从不同的视图作为 actionSheet 调用):
/**
asks for permission to show notifications, (only once) if user denied there is no information about this , it is just not grantedand the user then has to go to settings to allow notifications
if permission is granted it returns true
*/
func askForNotificationPermission(userGrantedPremission: @escaping (Bool)->())
{
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in
if success {
userGrantedPremission(true)
} else if let error = error {
userGrantedPremission(false)
}
}
}
仅当用户允许通知权限时,我的 TimerView 结构才会被调用
askForNotificationPermission() { (success) -> () in
if success
{
// permission granted
...
// passing information about the countdown duration and others..
...
userConfirmedSelection = true // indicates to calling view onDismiss that user wishes to start a countdown
showSheetView = false // closes this actionSheet
}
else
{
// permission denied
showNotificationPermissionIsNeededButton = true
}
}
来自上一个视图
.sheet(isPresented: $showCountDownOrTimerSheet, onDismiss: {
// what to do when sheet was dismissed
if userConfirmedChange
{
// go to timer activity and pass startTimerInformation to activity
programmaticNavigationDestination = .timer
}
}) {
CountdownOrTimerSheet(startTimerInformation: Binding($startTimerInformation)!, showSheetView: $showCountDownOrTimerSheet, userConfirmedSelection: $userConfirmedChange)
}
...
NavigationLink("timer", destination:
TimerView(...),
tag: .timer, selection: $programmaticNavigationDestination)
.frame(width: 0, height: 0)
在我的 TimerView 的初始化中,通知终于被注册了
self.endDate = Date().fromTimeMillis(timeMillis: timerServiceRelevantVars.endOfCountDownInMilliseconds_date)
// set a countdown Finished notification to the end of countdown
let calendar = Calendar.current
let notificationComponents = calendar.dateComponents([.hour, .minute, .second], from: endDate)
let trigger = UNCalendarNotificationTrigger(dateMatching: notificationComponents, repeats: false)
let content = UNMutableNotificationContent()
content.title = "Countdown Finished"
content.subtitle = "the countdown finished"
content.sound = UNNotificationSound.defaultCritical
// choose a random identifier
let request2 = UNNotificationRequest(identifier: "endCountdown", content: content, trigger: trigger)
// add the notification request
UNUserNotificationCenter.current().add(request2)
{
(error) in
if let error = error
{
print("Uh oh! We had an error: \(error)")
}
}
如上所述,当用户在除我自己的应用之外的任何地方时,通知都会按预期显示。然而,TimerView 显示有关倒计时的信息,并且最好是用户设备上的活动视图。因此,我需要能够在此处接收通知,但也需要在我的应用程序中的其他任何地方接收通知,因为用户还可以在我的应用程序中的其他地方导航。如何实现?
在this example中完成了类似的事情,不幸的是不是用swiftUI写的,而是用以前的通用语言写的。我不明白这是如何完成的,或者如何完成的。我在互联网上没有找到任何关于这个的东西。希望你能帮助我。
参考文档:
Scheduling and Handling Local Notifications
关于当您的应用程序位于前台时处理通知的部分:
If a notification arrives while your app is in the foreground, you can
silence that notification or tell the system to continue to display
the notification interface. The system silences notifications for
foreground apps by default, delivering the notification’s data
directly to your app...
据此,您必须为 UNUserNotificationCenter
实现委托并调用 completionHandler
来告知您希望如何处理通知。
我建议你这样做,在 AppDelegate
上你为 UNUserNotificationCenter
分配委托,因为文档说它必须在应用程序完成启动之前完成(请注意文档说委托应该在应用程序完成启动之前设置):
// AppDelegate.swift
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
UNUserNotificationCenter.current().delegate = self
return true
}
}
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
// Here we actually handle the notification
print("Notification received with identifier \(notification.request.identifier)")
// So we call the completionHandler telling that the notification should display a banner and play the notification sound - this will happen while the app is in foreground
completionHandler([.banner, .sound])
}
}
并且您可以通过在 App
场景中使用 UIApplicationDelegateAdaptor
告诉 SwiftUI 使用此 AppDelegate
:
@main
struct YourApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
此方法类似于 Apple 的 Fruta:使用 SwiftUI 构建功能丰富的应用程序
https://developer.apple.com/documentation/swiftui/fruta_building_a_feature-rich_app_with_swiftui
Apple 以这种方式使用应用内购买
此 class 包含您与通知相关的所有代码。
class LocalNotificaitonCenter: NSObject, ObservableObject {
// .....
}
在您的 @main
App 结构中,将 LocalNotificaitonCenter
定义为 @StateObject
并将其作为 environmentObject
传递给子视图
@main
struct YourApp: App {
@Environment(\.scenePhase) private var scenePhase
@StateObject var localNotificaitonCenter = LocalNotificaitonCenter()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(localNotificaitonCenter)
}
}
}
就是这样!
我正在使用包含倒计时功能的 SwiftUI 为 iOS 重新开发一个 android 应用程序。当倒计时结束时,应该通知用户倒计时结束。 Notification 应该具有一定的侵入性,并且适用于不同的场景,例如当用户未主动使用 phone 时,当用户正在使用我的应用程序时以及当用户正在使用其他应用程序时。我决定使用本地通知来实现这一点,这是 android 的工作方法。 (如果这种方法完全错误,请告诉我什么是最佳实践)
但是,当用户当前正在使用我的应用程序时,我无法收到通知。通知只显示在消息中心(所有通知排队的地方),但不会主动弹出。
到目前为止,这是我的代码: 要求用户获得在我的 CountdownOrTimerSheet 结构中使用通知的权限(从不同的视图作为 actionSheet 调用):
/**
asks for permission to show notifications, (only once) if user denied there is no information about this , it is just not grantedand the user then has to go to settings to allow notifications
if permission is granted it returns true
*/
func askForNotificationPermission(userGrantedPremission: @escaping (Bool)->())
{
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in
if success {
userGrantedPremission(true)
} else if let error = error {
userGrantedPremission(false)
}
}
}
仅当用户允许通知权限时,我的 TimerView 结构才会被调用
askForNotificationPermission() { (success) -> () in
if success
{
// permission granted
...
// passing information about the countdown duration and others..
...
userConfirmedSelection = true // indicates to calling view onDismiss that user wishes to start a countdown
showSheetView = false // closes this actionSheet
}
else
{
// permission denied
showNotificationPermissionIsNeededButton = true
}
}
来自上一个视图
.sheet(isPresented: $showCountDownOrTimerSheet, onDismiss: {
// what to do when sheet was dismissed
if userConfirmedChange
{
// go to timer activity and pass startTimerInformation to activity
programmaticNavigationDestination = .timer
}
}) {
CountdownOrTimerSheet(startTimerInformation: Binding($startTimerInformation)!, showSheetView: $showCountDownOrTimerSheet, userConfirmedSelection: $userConfirmedChange)
}
...
NavigationLink("timer", destination:
TimerView(...),
tag: .timer, selection: $programmaticNavigationDestination)
.frame(width: 0, height: 0)
在我的 TimerView 的初始化中,通知终于被注册了
self.endDate = Date().fromTimeMillis(timeMillis: timerServiceRelevantVars.endOfCountDownInMilliseconds_date)
// set a countdown Finished notification to the end of countdown
let calendar = Calendar.current
let notificationComponents = calendar.dateComponents([.hour, .minute, .second], from: endDate)
let trigger = UNCalendarNotificationTrigger(dateMatching: notificationComponents, repeats: false)
let content = UNMutableNotificationContent()
content.title = "Countdown Finished"
content.subtitle = "the countdown finished"
content.sound = UNNotificationSound.defaultCritical
// choose a random identifier
let request2 = UNNotificationRequest(identifier: "endCountdown", content: content, trigger: trigger)
// add the notification request
UNUserNotificationCenter.current().add(request2)
{
(error) in
if let error = error
{
print("Uh oh! We had an error: \(error)")
}
}
如上所述,当用户在除我自己的应用之外的任何地方时,通知都会按预期显示。然而,TimerView 显示有关倒计时的信息,并且最好是用户设备上的活动视图。因此,我需要能够在此处接收通知,但也需要在我的应用程序中的其他任何地方接收通知,因为用户还可以在我的应用程序中的其他地方导航。如何实现?
在this example中完成了类似的事情,不幸的是不是用swiftUI写的,而是用以前的通用语言写的。我不明白这是如何完成的,或者如何完成的。我在互联网上没有找到任何关于这个的东西。希望你能帮助我。
参考文档:
Scheduling and Handling Local Notifications
关于当您的应用程序位于前台时处理通知的部分:
If a notification arrives while your app is in the foreground, you can silence that notification or tell the system to continue to display the notification interface. The system silences notifications for foreground apps by default, delivering the notification’s data directly to your app...
据此,您必须为 UNUserNotificationCenter
实现委托并调用 completionHandler
来告知您希望如何处理通知。
我建议你这样做,在 AppDelegate
上你为 UNUserNotificationCenter
分配委托,因为文档说它必须在应用程序完成启动之前完成(请注意文档说委托应该在应用程序完成启动之前设置):
// AppDelegate.swift
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
UNUserNotificationCenter.current().delegate = self
return true
}
}
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
// Here we actually handle the notification
print("Notification received with identifier \(notification.request.identifier)")
// So we call the completionHandler telling that the notification should display a banner and play the notification sound - this will happen while the app is in foreground
completionHandler([.banner, .sound])
}
}
并且您可以通过在 App
场景中使用 UIApplicationDelegateAdaptor
告诉 SwiftUI 使用此 AppDelegate
:
@main
struct YourApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
此方法类似于 Apple 的 Fruta:使用 SwiftUI 构建功能丰富的应用程序
https://developer.apple.com/documentation/swiftui/fruta_building_a_feature-rich_app_with_swiftui
Apple 以这种方式使用应用内购买
此 class 包含您与通知相关的所有代码。
class LocalNotificaitonCenter: NSObject, ObservableObject {
// .....
}
在您的 @main
App 结构中,将 LocalNotificaitonCenter
定义为 @StateObject
并将其作为 environmentObject
传递给子视图
@main
struct YourApp: App {
@Environment(\.scenePhase) private var scenePhase
@StateObject var localNotificaitonCenter = LocalNotificaitonCenter()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(localNotificaitonCenter)
}
}
}
就是这样!