如何使用 SwiftUI 打开通知操作的自定义视图?
Posted
技术标签:
【中文标题】如何使用 SwiftUI 打开通知操作的自定义视图?【英文标题】:How to open a custom view on notification action using SwiftUI? 【发布时间】:2021-02-08 22:00:21 【问题描述】:我正在尝试获取通知操作以打开自定义视图。我的通知基本上是新闻,我希望用户在点击“阅读通知”操作时转到显示简单文本的页面(出于此问题的目的)。 我已经尝试了大量的教程,但它们都使用了一些现有的视图,比如“imagePicker”,默认情况下已经启用了大量的东西,我不知道我需要添加到我的自定义视图中以使其工作的所有东西.像 UIViewControllerRepresentable、协调器或其他我可能需要的东西。
这是我处理通知的主要 swift 文件。(通知工作正常) 文件末尾是扩展名 AppDelegate: UNUserNotificationCenterDelegate ,取自我正在遵循的教程,它创建了一个 NewsItem,我也将在此处包含它。但由于我使用的是 SwiftUI 而不是 UIKit,所以我无法按照我设法找到的任何教程来让它按照我想要的方式工作。
我已包含完整的应用程序委托扩展和 newsItem 只是为了使此处的代码可编译,但我将需要更改的部分放在注释块中。
import SwiftUI
import UserNotifications
enum Identifiers
static let viewAction = "VIEW_IDENTIFIER"
static let readableCategory = "READABLE"
@main
struct MyApp: App
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene
WindowGroup
TabView
NavigationView
ContentView()
.tabItem
Label("Home", systemImage : "house")
class AppDelegate: NSObject, UIApplicationDelegate
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool
UNUserNotificationCenter.current().delegate = self// set the delegate
registerForPushNotifications()
return true
func application( // registers for notifications and gets token
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
)
let tokenParts = deviceToken.map data in String(format: "%02.2hhx", data)
let token = tokenParts.joined()
print("device token : \(token)")
//handles sucessful register for notifications
func application( //handles unsucessful register for notifications
_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error
)
print("Failed to register: \(error)")
//handles unsucessful register for notifications
func application( //handles notifications when app in foreground
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler:
@escaping (UIBackgroundFetchResult) -> Void
)
guard let aps = userInfo["aps"] as? [String: AnyObject] else
completionHandler(.failed)
return
print("new notification received")
//handles notifications when app in foreground
func registerForPushNotifications()
UNUserNotificationCenter.current()
.requestAuthorization(options: [.alert, .sound, .badge]) [weak self] granted, _ in
print("permission granted: \(granted)")
guard granted else return
let viewAction = UNNotificationAction(
identifier: Identifiers.viewAction,
title: "Mark as read",
options: [.foreground])
let readableNotification = UNNotificationCategory(
identifier: Identifiers.readable,
actions: [viewAction2],
intentIdentifiers: [],
options: [])
UNUserNotificationCenter.current().setNotificationCategories([readableNotification])
self?.getNotificationSettings()
func getNotificationSettings()
UNUserNotificationCenter.current().getNotificationSettings settings in
guard settings.authorizationStatus == .authorized else return
DispatchQueue.main.async
UIApplication.shared.registerForRemoteNotifications()
print("notification settings: \(settings)")
// MARK: - UNUserNotificationCenterDelegate
extension AppDelegate: UNUserNotificationCenterDelegate
func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
)
let userInfo = response.notification.request.content.userInfo
if let aps = userInfo["aps"] as? [String: AnyObject],
let newsItem = NewsItem.makeNewsItem(aps)
(window?.rootViewController as? UITabBarController)?.selectedIndex = 1
if response.actionIdentifier == Identifiers.viewAction,
let url = URL(string: newsItem.link)
let safari = SFSafariViewController(url: url)
window?.rootViewController?.present(safari, animated: true, completion: nil)
completionHandler()
// MARK: - UNUserNotificationCenterDelegate
extension AppDelegate: UNUserNotificationCenterDelegate
func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
)
let userInfo = response.notification.request.content.userInfo
if let aps = userInfo["aps"] as? [String: AnyObject],
/*
let newsItem = NewsItem.makeNewsItem(aps)
(window?.rootViewController as? UITabBarController)?.selectedIndex = 1
if response.actionIdentifier == Identifiers.viewAction,
let url = URL(string: newsItem.link)
let safari = SFSafariViewController(url: url)
window?.rootViewController?.present(safari, animated: true, completion: nil)
*/
completionHandler()
这是教程中的 NewsItem.swift 以防万一,但这是我不需要或不想使用的文件。
import Foundation
struct NewsItem: Codable
let title: String
let date: Date
let link: String
@discardableResult
static func makeNewsItem(_ notification: [String: AnyObject]) -> NewsItem?
guard
let news = notification["alert"] as? String,
let url = notification["link_url"] as? String
else
return nil
let newsItem = NewsItem(title: news, date: Date(), link: url)
let newsStore = NewsStore.shared
newsStore.add(item: newsItem)
NotificationCenter.default.post(
name: NewsFeedTableViewController.refreshNewsFeedNotification,
object: self)
return newsItem
简化的内容视图
import SwiftUI
struct ContentView: View
var body: some View
VStack
Text(DataForApp.welcomeText)
.font(.title)
.bold()
.multilineTextAlignment(.center)
.foregroundColor(.secondary)
.shadow(radius: 8 )
.navigationTitle("My Mobile App")
现在我的目标是使用此 MyView,一旦用户点击“标记为已读”操作,我希望此视图显示。
import SwiftUI
struct MyView: View
var body: some View
Text("Notification text here")
显然 MyView 不包含它需要的任何东西,但我不想发布我在这里尝试过的代码,因为我尝试了 200 种不同的东西,由于它们都不起作用,我意识到我什至没有接近正确的轨道。
【问题讨论】:
"aps": "alert": "Breking News!", "sound": "default", "link_url": "raywenderlich.com", "category": "READABLE" 这是我发送给测试的通知 【参考方案1】:我通过使用我创建的名为 NotificationManager
的 @ObservableObject 解决了这个问题——它存储了最新通知的文本(如果你愿意,你可以扩展它来存储一个数组)并且提供一个绑定,用于告诉应用是否根据是否有要显示的通知在堆栈中显示新视图。
这个NotificationManager
必须是ContentView
上的@ObservedObject 才能工作,因为ContentView 需要观察currentNotificationText
的状态变化,这是一个@Published 属性。
ContentView
有一个不可见的NavigationLink
(通过.overlay
和EmptyView
),只有在有通知的情况下才会被激活。
在 App Delegate 方法中,我只是将通知传递给一个简单的函数 handleNotification
,该函数解析 aps
并将生成的 String
放入 NotificationManager
。您还可以使用更强大的功能轻松地对此进行扩充,包括解析来自 aps
的其他字段
import SwiftUI
import UserNotifications
//new class to store notification text and to tell the NavigationView to go to a new page
class NotificationManager : ObservableObject
@Published var currentNotificationText : String?
var navigationBindingActive : Binding<Bool>
.init () -> Bool in
self.currentNotificationText != nil
set: (newValue) in
if !newValue self.currentNotificationText = nil
enum Identifiers
static let viewAction = "VIEW_IDENTIFIER"
static let readableCategory = "READABLE"
@main
struct MyApp: App
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene
WindowGroup
TabView
NavigationView
ContentView(notificationManager: appDelegate.notificationManager) //pass the notificationManager as a dependency
.tabItem
Label("Home", systemImage : "house")
class AppDelegate: NSObject, UIApplicationDelegate
var notificationManager = NotificationManager() //here's where notificationManager is stored
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool
UNUserNotificationCenter.current().delegate = self// set the delegate
registerForPushNotifications()
return true
func application( // registers for notifications and gets token
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
)
let tokenParts = deviceToken.map data in String(format: "%02.2hhx", data)
let token = tokenParts.joined()
print("device token : \(token)")
//handles sucessful register for notifications
func application( //handles unsucessful register for notifications
_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error
)
print("Failed to register: \(error)")
//handles unsucessful register for notifications
func application( //handles notifications when app in foreground
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler:
@escaping (UIBackgroundFetchResult) -> Void
)
guard let aps = userInfo["aps"] as? [String: AnyObject] else
completionHandler(.failed)
return
print("new notification received")
handleNotification(aps: aps)
completionHandler(.noData)
//handles notifications when app in foreground
func registerForPushNotifications()
UNUserNotificationCenter.current()
.requestAuthorization(options: [.alert, .sound, .badge]) [weak self] granted, _ in
print("permission granted: \(granted)")
guard granted else return
let viewAction = UNNotificationAction(
identifier: Identifiers.viewAction,
title: "Mark as read",
options: [.foreground])
let readableNotification = UNNotificationCategory(
identifier: Identifiers.readableCategory,
actions: [viewAction],
intentIdentifiers: [],
options: [])
UNUserNotificationCenter.current().setNotificationCategories([readableNotification])
self?.getNotificationSettings()
func getNotificationSettings()
UNUserNotificationCenter.current().getNotificationSettings settings in
guard settings.authorizationStatus == .authorized else return
DispatchQueue.main.async
UIApplication.shared.registerForRemoteNotifications()
print("notification settings: \(settings)")
// MARK: - UNUserNotificationCenterDelegate
extension AppDelegate: UNUserNotificationCenterDelegate
func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
)
let userInfo = response.notification.request.content.userInfo
if let aps = userInfo["aps"] as? [String: AnyObject]
handleNotification(aps: aps)
extension AppDelegate
@discardableResult func handleNotification(aps: [String:Any]) -> Bool
guard let alert = aps["alert"] as? String else //get the "alert" field
return false
self.notificationManager.currentNotificationText = alert
return true
struct ContentView: View
@ObservedObject var notificationManager : NotificationManager
var body: some View
VStack
Text("Welcome")
.font(.title)
.bold()
.multilineTextAlignment(.center)
.foregroundColor(.secondary)
.shadow(radius: 8 )
.navigationTitle("My Mobile App")
.overlay(NavigationLink(destination: MyView(text: notificationManager.currentNotificationText ?? ""), isActive: notificationManager.navigationBindingActive, label:
EmptyView()
))
struct MyView: View
var text : String
var body: some View
Text(text)
(我必须用您问题中的原始代码修复一些拼写错误/编译错误,因此请确保如果您使用它,则直接复制和粘贴以获得正确的方法签名等.)
【讨论】:
上帝保佑你,伙计:)我从那里得到它。 “ContentView 有一个不可见的 NavigationLink(通过带有 EmptyView 的 .overlay),只有在有通知的情况下才会被激活。”这个小部分让我避免了整个控制器部分的复杂性。 您知道是否可以在应用程序处于后台时对收到的推送通知执行操作?在 android 中,我有 onMessageReceived() 并且我将所有通知保存到一个列表中,以便在收到通知后立即保存。在 ios 中,如果应用程序在前台,我可以执行操作,并且在用户点击通知之前似乎无法执行操作。所以如果用户在没有点击的情况下打开应用程序是一个问题,他不会有通知历史记录。 将虚拟机放入 appDelegate 并帮助了我的好主意,尽管它使设置看起来有点难看。以上是关于如何使用 SwiftUI 打开通知操作的自定义视图?的主要内容,如果未能解决你的问题,请参考以下文章