我正在用SwiftUI重新开发一个适用于iOS的android应用程序,该应用程序包含倒计时功能。当倒计时结束时,用户应该注意倒计时的结束。通知应具有一定的侵入性,并在不同的情况下工作,例如,当用户没有积极使用手机时,当用户使用我的应用程序时,以及当用户使用另一个应用程序时。我决定使用本地通知来实现这一点,这是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的init中,通知最终被注册为
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显示关于倒计时的信息,并且优选地是用户设备上的活动视图。因此,我需要能够在这里接收通知,也可以在我的应用程序中的其他地方接收通知,因为用户也可以在应用程序中导航到其他地方。如何才能做到这一点?
在这个例子中,类似的事情已经完成了,不幸的是,它不是用swiftUI编写的,而是用以前的通用语言编写的。我不明白这是怎么做到的,也不明白如何做到的。。我在网上没有找到这方面的任何信息。。我希望你能帮我。
参考文档:
调度和处理本地通知
在关于当您的应用程序处于前台时处理通知的部分:
如果在应用程序处于前台时收到通知,您可以使该通知静音或告诉系统继续显示通知接口。系统会使的通知静音默认情况下前台应用程序,传递通知的数据直接连接到您的应用程序。。。
根据这一点,您必须为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()
}
}
}
创建代理类
import Foundation
import SwiftUI
class NotificationDelegate: NSObject , UNUserNotificationCenterDelegate{
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
print("Notification tapped`")
//Do what you need to do here once the app is opened by tapping on the notification.
completionHandler()
}
}
将以下内容添加到您的应用程序文件
@main
struct myApp: App {
private var notificDelegate : NotificationDelegate = NotificationDelegate()
init(){
UNUserNotificationCenter.current().delegate = notificDelegate
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
这种方法类似于苹果的Fruta:使用SwiftUI 构建功能丰富的应用程序
https://developer.apple.com/documentation/swiftui/fruta_building_a_feature-rich_app_with_swiftui
苹果以这种方式使用应用内购买
此类保存与Notification相关的所有代码。
class LocalNotificaitonCenter: NSObject, ObservableObject {
// .....
}
在@main
应用程序结构中,将LocalNotificaitonCenter
定义为@StateObject
,并将其作为environmentObject
传递给子视图
@main
struct YourApp: App {
@Environment(.scenePhase) private var scenePhase
@StateObject var localNotificaitonCenter = LocalNotificaitonCenter()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(localNotificaitonCenter)
}
}
}
就是这样!