Я пытаюсь получить действие уведомления, чтобы открыть настраиваемое представление. Мои уведомления в основном являются новостями, и я хочу, чтобы пользователь переходил на страницу, отображающую простой текст (для целей этого вопроса), когда он нажимает действие «прочитать уведомление». Я перепробовал множество руководств, но все они используют некоторые существующие представления, такие как «imagePicker», в которых по умолчанию уже включена масса функций, и я не знаю, что мне нужно добавить в свое настраиваемое представление, чтобы это работало. . Например, UIViewControllerRepresentable, координатор или что-то еще, что мне может понадобиться.

Это мой основной быстрый файл, который обрабатывает уведомления. (Уведомления работают нормально). В конце файла есть расширение 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
  }
}

Упрощенный ContentView

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 разных вещей, и, поскольку ни одна из них не работает, я понимаю, что я даже не приблизился к правильному пути.

0
Slobodan Margetić 9 Фев 2021 в 01:00

1 ответ

Лучший ответ

Я решил эту проблему с помощью созданного мной @ObservableObject под названием NotificationManager - в нем хранится текст самого последнего уведомления (вы можете развернуть его, чтобы сохранить массив, если хотите) и предоставляет привязка, чтобы сообщить приложению, следует ли отображать новое представление в стеке в зависимости от того, отображается ли уведомление.

Этот NotificationManager должен быть @ObservedObject на ContentView, чтобы это работало, поскольку ContentView должен отслеживать изменения в состоянии currentNotificationText, которое является свойством @Published.

ContentView имеет невидимый NavigationLink (через .overlay с EmptyView), который активируется только в случае уведомления.

В методах делегирования приложения я просто передаю уведомление простой функции 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)
    }
}

(Мне пришлось исправить ряд опечаток / ошибок компиляции с помощью исходного кода в вашем вопросе, поэтому убедитесь, что если вы используете это, вы копируете и вставляете напрямую, чтобы получить правильные сигнатуры методов и т. д.) < / em>

1
jnpdx 11 Фев 2021 в 17:52