美文网首页
系统推送的集成(二十二) —— 关于本地通知的详细解析(二)

系统推送的集成(二十二) —— 关于本地通知的详细解析(二)

作者: 刀客传奇 | 来源:发表于2021-05-24 10:07 被阅读0次

    版本记录

    版本号 时间
    V1.0 2021.05.24 星期一

    前言

    我们做APP很多时候都需要推送功能,以直播为例,如果你关注的主播开播了,那么就需要向关注这个主播的人发送开播通知,提醒用户去看播,这个只是一个小的方面,具体应用根据公司的业务逻辑而定。前面已经花了很多篇幅介绍了极光推送,其实极光推送无非就是将我们客户端和服务端做的很多东西封装了一下,节省了我们很多处理逻辑和流程,这一篇开始,我们就利用系统的原生推送类结合工程实践说一下系统推送的集成,希望我的讲解能让大家很清楚的理解它。感兴趣的可以看上面几篇。
    1. 系统推送的集成(一) —— 基本集成流程(一)
    2. 系统推送的集成(二) —— 推送遇到的几个坑之BadDeviceToken问题(一)
    3. 系统推送的集成(三) —— 本地和远程通知编程指南之你的App的通知 - 本地和远程通知概览(一)
    4. 系统推送的集成(四) —— 本地和远程通知编程指南之你的App的通知 - 管理您的应用程序的通知支持(二)
    5. 系统推送的集成(五) —— 本地和远程通知编程指南之你的App的通知 - 调度和处理本地通知(三)
    6. 系统推送的集成(六) —— 本地和远程通知编程指南之你的App的通知 - 配置远程通知支持(四)
    7. 系统推送的集成(七) —— 本地和远程通知编程指南之你的App的通知 - 修改和显示通知(五)
    8. 系统推送的集成(八) —— 本地和远程通知编程指南之苹果推送通知服务APNs - APNs概览(一)
    9. 系统推送的集成(九) —— 本地和远程通知编程指南之苹果推送通知服务APNs - 创建远程通知Payload(二)
    10. 系统推送的集成(十) —— 本地和远程通知编程指南之苹果推送通知服务APNs - 与APNs通信(三)
    11. 系统推送的集成(十一) —— 本地和远程通知编程指南之苹果推送通知服务APNs - Payload Key参考(四)
    12. 系统推送的集成(十二) —— 本地和远程通知编程指南之Legacy信息 - 二进制Provider API(一)
    13. 系统推送的集成(十三) —— 本地和远程通知编程指南之Legacy信息 - Legacy通知格式(二)
    14. 系统推送的集成(十四) —— 发送和处理推送通知流程详解(一)
    15. 系统推送的集成(十五) —— 发送和处理推送通知流程详解(二)
    16. 系统推送的集成(十六) —— 自定义远程通知(一)
    17. 系统推送的集成(十七) —— APNs从工程配置到自定义通知UI全流程解析(一)
    18. 系统推送的集成(十八) —— APNs从工程配置到自定义通知UI全流程解析(二)
    19. 系统推送的集成(十九) —— APNs配置接收和处理的简单入门(一)
    20. 系统推送的集成(二十) —— APNs配置接收和处理的简单入门(二)
    21. 系统推送的集成(二十一) —— 关于本地通知的详细解析(一)

    源码

    1. Swift

    首先看下工程组织结构

    下面就是源码啦

    1. TaskManager.swift
    
    import Foundation
    
    class TaskManager: ObservableObject {
      static let shared = TaskManager()
      let taskPersistenceManager = TaskPersistenceManager()
    
      @Published var tasks: [Task] = []
    
      init() {
        loadTasks()
      }
    
      func save(task: Task) {
        tasks.append(task)
        DispatchQueue.global().async {
          self.taskPersistenceManager.save(tasks: self.tasks)
        }
        if task.reminderEnabled {
          NotificationManager.shared.scheduleNotification(task: task)
        }
      }
    
      func loadTasks() {
        self.tasks = taskPersistenceManager.loadTasks()
      }
    
      func addNewTask(_ taskName: String, _ reminder: Reminder?) {
        if let reminder = reminder {
          save(task: Task(name: taskName, reminderEnabled: true, reminder: reminder))
        } else {
          save(task: Task(name: taskName, reminderEnabled: false, reminder: Reminder()))
        }
      }
    
      func remove(task: Task) {
        tasks.removeAll {
          $0.id == task.id
        }
        DispatchQueue.global().async {
          self.taskPersistenceManager.save(tasks: self.tasks)
        }
        if task.reminderEnabled {
          NotificationManager.shared.removeScheduledNotification(task: task)
        }
      }
    
      func markTaskComplete(task: Task) {
        if let row = tasks.firstIndex(where: { $0.id == task.id }) {
          var updatedTask = task
          updatedTask.completed = true
          tasks[row] = updatedTask
        }
      }
    }
    
    2. NotificationManager.swift
    
    import Foundation
    import UserNotifications
    import CoreLocation
    
    enum NotificationManagerConstants {
      static let timeBasedNotificationThreadId =
        "TimeBasedNotificationThreadId"
      static let calendarBasedNotificationThreadId =
        "CalendarBasedNotificationThreadId"
      static let locationBasedNotificationThreadId =
        "LocationBasedNotificationThreadId"
    }
    
    class NotificationManager: ObservableObject {
      static let shared = NotificationManager()
      @Published var settings: UNNotificationSettings?
    
      func requestAuthorization(completion: @escaping  (Bool) -> Void) {
        UNUserNotificationCenter.current()
          .requestAuthorization(options: [.alert, .sound, .badge]) { granted, _  in
            self.fetchNotificationSettings()
            completion(granted)
          }
      }
    
      func fetchNotificationSettings() {
        // 1
        UNUserNotificationCenter.current().getNotificationSettings { settings in
          // 2
          DispatchQueue.main.async {
            self.settings = settings
          }
        }
      }
    
      func removeScheduledNotification(task: Task) {
        UNUserNotificationCenter.current()
          .removePendingNotificationRequests(withIdentifiers: [task.id])
      }
    
      // 1
      func scheduleNotification(task: Task) {
        // 2
        let content = UNMutableNotificationContent()
        content.title = task.name
        content.body = "Gentle reminder for your task!"
        content.categoryIdentifier = "OrganizerPlusCategory"
        let taskData = try? JSONEncoder().encode(task)
        if let taskData = taskData {
          content.userInfo = ["Task": taskData]
        }
    
        // 3
        var trigger: UNNotificationTrigger?
        switch task.reminder.reminderType {
        case .time:
          if let timeInterval = task.reminder.timeInterval {
            trigger = UNTimeIntervalNotificationTrigger(
              timeInterval: timeInterval,
              repeats: task.reminder.repeats)
          }
          content.threadIdentifier =
            NotificationManagerConstants.timeBasedNotificationThreadId
        case .calendar:
          if let date = task.reminder.date {
            trigger = UNCalendarNotificationTrigger(
              dateMatching: Calendar.current.dateComponents(
                [.day, .month, .year, .hour, .minute],
                from: date),
              repeats: task.reminder.repeats)
          }
          content.threadIdentifier =
            NotificationManagerConstants.calendarBasedNotificationThreadId
        case .location:
          // 1
          guard CLLocationManager().authorizationStatus == .authorizedWhenInUse else {
            return
          }
          // 2
          if let location = task.reminder.location {
            // 3
            let center = CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude)
            let region = CLCircularRegion(center: center, radius: location.radius, identifier: task.id)
            trigger = UNLocationNotificationTrigger(region: region, repeats: task.reminder.repeats)
          }
          content.threadIdentifier =
            NotificationManagerConstants.locationBasedNotificationThreadId
        }
    
        // 4
        if let trigger = trigger {
          let request = UNNotificationRequest(
            identifier: task.id,
            content: content,
            trigger: trigger)
          // 5
          UNUserNotificationCenter.current().add(request) { error in
            if let error = error {
              print(error)
            }
          }
        }
      }
    }
    
    3. TaskPersistenceManager.swift
    
    import Foundation
    
    class TaskPersistenceManager {
      enum FileConstants {
        static let tasksFileName = "tasks.json"
      }
    
      func save(tasks: [Task]) {
        do {
          let documentsDirectory = getDocumentsDirectory()
          let storageURL = documentsDirectory.appendingPathComponent(FileConstants.tasksFileName)
          let tasksData = try JSONEncoder().encode(tasks)
          do {
            try tasksData.write(to: storageURL)
          } catch {
            print("Couldn't write to File Storage")
          }
        } catch {
          print("Couldn't encode tasks data")
        }
      }
    
      func loadTasks() -> [Task] {
        let documentsDirectory = getDocumentsDirectory()
        let storageURL = documentsDirectory.appendingPathComponent(FileConstants.tasksFileName)
        guard
          let taskData = try? Data(contentsOf: storageURL),
          let tasks = try? JSONDecoder().decode([Task].self, from: taskData)
        else {
          return []
        }
    
        return tasks
      }
    
      func getDocumentsDirectory() -> URL {
        let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        return paths[0]
      }
    }
    
    4. LocationManager.swift
    
    import CoreLocation
    
    class LocationManager: NSObject, ObservableObject {
      var locationManager = CLLocationManager()
      @Published var authorized = false
    
      override init() {
        super.init()
        locationManager.delegate = self
        if locationManager.authorizationStatus == .authorizedWhenInUse {
          authorized = true
          locationManager.startMonitoringSignificantLocationChanges()
        }
      }
    
      func requestAuthorization() {
        locationManager.requestWhenInUseAuthorization()
      }
    }
    
    // MARK: - CLLocationManagerDelegate
    extension LocationManager: CLLocationManagerDelegate {
      func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        if locationManager.authorizationStatus == .authorizedWhenInUse ||
          locationManager.authorizationStatus == .authorizedAlways {
          authorized = true
        } else {
          authorized = false
        }
      }
    }`
    
    5. TaskListView.swift
    
    import SwiftUI
    
    struct TaskListView: View {
      @ObservedObject var taskManager = TaskManager.shared
      @State var showNotificationSettingsUI = false
    
      var body: some View {
        ZStack {
          VStack {
            HStack {
              Spacer()
              Text("Organizer Plus")
                .font(.title)
                .foregroundColor(.pink)
              Spacer()
              Button(
                action: {
                  // 1
                  NotificationManager.shared.requestAuthorization { granted in
                    // 2
                    if granted {
                      showNotificationSettingsUI = true
                    }
                  }
                },
                label: {
                  Image(systemName: "bell")
                    .font(.title)
                    .accentColor(.pink)
                })
                .padding(.trailing)
                .sheet(isPresented: $showNotificationSettingsUI) {
                  NotificationSettingsView()
                }
            }
            .padding()
            if taskManager.tasks.isEmpty {
              Spacer()
              Text("No Tasks!")
                .foregroundColor(.pink)
                .font(.title3)
              Spacer()
            } else {
              List(taskManager.tasks) { task in
                TaskCell(task: task)
              }
              .padding()
            }
          }
          AddTaskView()
        }
      }
    }
    
    struct ContentView_Previews: PreviewProvider {
      static var previews: some View {
        TaskListView()
      }
    }
    
    struct TaskCell: View {
      var task: Task
    
      var body: some View {
        HStack {
          Button(
            action: {
              TaskManager.shared.markTaskComplete(task: task)
              DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
                TaskManager.shared.remove(task: task)
              }
            }, label: {
              Image(systemName: task.completed ? "checkmark.circle.fill" : "circle")
                .resizable()
                .frame(width: 20, height: 20)
                .accentColor(.pink)
            })
          if task.completed {
            Text(task.name)
              .strikethrough()
              .foregroundColor(.pink)
          } else {
            Text(task.name)
              .foregroundColor(.pink)
          }
        }
      }
    }
    
    struct AddTaskView: View {
      @State var showCreateTaskView = false
    
      var body: some View {
        VStack {
          Spacer()
          HStack {
            Spacer()
            Button(
              action: {
                showCreateTaskView = true
              }, label: {
                Text("+")
                  .font(.largeTitle)
                  .multilineTextAlignment(.center)
                  .frame(width: 30, height: 30)
                  .foregroundColor(Color.white)
                  .padding()
              })
              .background(Color.pink)
              .cornerRadius(40)
              .padding()
              .sheet(isPresented: $showCreateTaskView) {
                CreateTaskView()
              }
          }
          .padding(.bottom)
        }
      }
    }
    
    6. CreateTaskView.swift
    
    import SwiftUI
    import MapKit
    
    struct CreateTaskView: View {
      @State var taskName: String = ""
      @State var reminderEnabled = false
      @State var selectedTrigger = ReminderType.time
      @State var timeDurationIndex: Int = 0
      @State private var dateTrigger = Date()
      @State private var shouldRepeat = false
      @State private var latitude: String = ""
      @State private var longitude: String = ""
      @State private var radius: String = ""
      @Environment(\.presentationMode) var presentationMode
    
      let triggers = ["Time", "Calendar", "Location"]
      let timeDurations: [Int] = Array(1...59)
      var body: some View {
        NavigationView {
          Form {
            Section {
              HStack {
                Spacer()
                Text("Add Task")
                  .font(.title)
                  .padding()
                Spacer()
                Button("Save") {
                  TaskManager.shared.addNewTask(taskName, makeReminder())
                  presentationMode.wrappedValue.dismiss()
                }
                .disabled(taskName.isEmpty ? true : false)
                .padding()
              }
              VStack {
                TextField("Enter name for the task", text: $taskName)
                  .padding(.vertical)
                Toggle(isOn: $reminderEnabled) {
                  Text("Add Reminder")
                }
                .padding(.vertical)
    
                if reminderEnabled {
                  ReminderView(
                    selectedTrigger: $selectedTrigger,
                    timeDurationIndex: $timeDurationIndex,
                    triggerDate: $dateTrigger,
                    shouldRepeat: $shouldRepeat,
                    latitude: $latitude,
                    longitude: $longitude,
                    radius: $radius)
                    .navigationBarHidden(true)
                    .navigationTitle("")
                }
                Spacer()
              }
              .padding()
            }
          }
          .navigationBarTitle("")
          .navigationBarHidden(true)
        }
      }
    
      func makeReminder() -> Reminder? {
        guard reminderEnabled else {
          return nil
        }
        var reminder = Reminder()
        reminder.reminderType = selectedTrigger
        switch selectedTrigger {
        case .time:
          reminder.timeInterval = TimeInterval(timeDurations[timeDurationIndex] * 60)
        case .calendar:
          reminder.date = dateTrigger
        case .location:
          if let latitude = Double(latitude),
            let longitude = Double(longitude),
            let radius = Double(radius) {
            reminder.location = LocationReminder(
              latitude: latitude,
              longitude: longitude,
              radius: radius)
          }
        }
        reminder.repeats = shouldRepeat
        return reminder
      }
    }
    
    struct CreateTaskView_Previews: PreviewProvider {
      static var previews: some View {
        CreateTaskView()
      }
    }
    
    struct ReminderView: View {
      @Binding var selectedTrigger: ReminderType
      @Binding var timeDurationIndex: Int
      @Binding var triggerDate: Date
      @Binding var shouldRepeat: Bool
      @Binding var latitude: String
      @Binding var longitude: String
      @Binding var radius: String
      @StateObject var locationManager = LocationManager()
    
      var body: some View {
        VStack {
          Picker("Notification Trigger", selection: $selectedTrigger) {
            Text("Time").tag(ReminderType.time)
            Text("Date").tag(ReminderType.calendar)
            Text("Location").tag(ReminderType.location)
          }
          .pickerStyle(SegmentedPickerStyle())
          .padding(.vertical)
          if selectedTrigger == ReminderType.time {
            Picker("Time Interval", selection: $timeDurationIndex) {
              ForEach(1 ..< 59) { i in
                if i == 1 {
                  Text("\(i) minute").tag(i)
                } else {
                  Text("\(i) minutes").tag(i)
                }
              }
              .navigationBarHidden(true)
              .padding(.vertical)
            }
          } else if selectedTrigger == ReminderType.calendar {
            DatePicker("Please enter a date", selection: $triggerDate)
              .labelsHidden()
              .padding(.vertical)
          } else {
            VStack {
              if !locationManager.authorized {
                Button(
                  action: {
                    locationManager.requestAuthorization()
                  },
                  label: {
                    Text("Request Location Authorization")
                  })
              } else {
                TextField("Enter Latitude", text: $latitude)
                TextField("Enter Longitude", text: $longitude)
                TextField("Enter Radius", text: $radius)
              }
            }
            .padding(.vertical)
          }
          Toggle(isOn: $shouldRepeat) {
            Text("Repeat Notification")
          }
        }
      }
    }
    
    7. NotificationSettingsView.swift
    
    import SwiftUI
    
    struct NotificationSettingsView: View {
      @ObservedObject var notificationManager = NotificationManager.shared
    
      var body: some View {
        VStack {
          Form {
            Section {
              HStack {
                Spacer()
                Text("Notification Settings")
                  .font(.title2)
                Spacer()
              }
            }
            Section {
              SettingRowView(
                setting: "Authorization Status",
                enabled: notificationManager.settings?.authorizationStatus == UNAuthorizationStatus.authorized)
              SettingRowView(
                setting: "Show in Notification Center",
                enabled: notificationManager.settings?.notificationCenterSetting == .enabled)
              SettingRowView(
                setting: "Sound Enabled?",
                enabled: notificationManager.settings?.soundSetting == .enabled)
              SettingRowView(
                setting: "Badges Enabled?",
                enabled: notificationManager.settings?.badgeSetting == .enabled)
              SettingRowView(
                setting: "Alerts Enabled?",
                enabled: notificationManager.settings?.alertSetting == .enabled)
              SettingRowView(
                setting: "Show on lock screen?",
                enabled: notificationManager.settings?.lockScreenSetting == .enabled)
              SettingRowView(
                setting: "Alert banners?",
                enabled: notificationManager.settings?.alertStyle == .banner)
              SettingRowView(
                setting: "Critical Alerts?",
                enabled: notificationManager.settings?.criticalAlertSetting == .enabled)
              SettingRowView(
                setting: "Siri Announcement?",
                enabled: notificationManager.settings?.announcementSetting == .enabled)
            }
          }
        }
      }
    }
    
    struct NotificationSettingsView_Previews: PreviewProvider {
      static var previews: some View {
        NotificationSettingsView()
      }
    }
    
    struct SettingRowView: View {
      var setting: String
      var enabled: Bool
      var body: some View {
        HStack {
          Text(setting)
          Spacer()
          if enabled {
            Image(systemName: "checkmark")
              .foregroundColor(.green)
          } else {
            Image(systemName: "xmark")
              .foregroundColor(.red)
          }
        }
        .padding()
      }
    }
    
    8. Task.swift
    
    import Foundation
    
    struct Task: Identifiable, Codable {
      var id = UUID().uuidString
      var name: String
      var completed = false
      var reminderEnabled = false
      var reminder: Reminder
    }
    
    enum ReminderType: Int, CaseIterable, Identifiable, Codable {
      case time
      case calendar
      case location
      var id: Int { self.rawValue }
    }
    
    struct Reminder: Codable {
      var timeInterval: TimeInterval?
      var date: Date?
      var location: LocationReminder?
      var reminderType: ReminderType = .time
      var repeats = false
    }
    
    struct LocationReminder: Codable {
      var latitude: Double
      var longitude: Double
      var radius: Double
    }
    
    9. AppMain.swift
    
    import SwiftUI
    
    @main
    struct AppMain: App {
      @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
      var body: some Scene {
        WindowGroup {
          TaskListView()
        }
      }
    }`
    
    10. AppDelegate.swift
    
    import UIKit
    
    class AppDelegate: NSObject, UIApplicationDelegate {
      func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
      ) -> Bool {
        configureUserNotifications()
        return true
      }
    }
    
    // MARK: - UNUserNotificationCenterDelegate
    extension AppDelegate: UNUserNotificationCenterDelegate {
      func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        willPresent notification: UNNotification,
        withCompletionHandler completionHandler: (UNNotificationPresentationOptions) -> Void
      ) {
        completionHandler(.banner)
      }
    
      private func configureUserNotifications() {
        UNUserNotificationCenter.current().delegate = self
        // 1
        let dismissAction = UNNotificationAction(
          identifier: "dismiss",
          title: "Dismiss",
          options: []
        )
        let markAsDone = UNNotificationAction(
          identifier: "markAsDone",
          title: "Mark As Done",
          options: []
        )
        // 2
        let category = UNNotificationCategory(
          identifier: "OrganizerPlusCategory",
          actions: [dismissAction, markAsDone],
          intentIdentifiers: [],
          options: []
        )
        // 3
        UNUserNotificationCenter.current().setNotificationCategories([category])
      }
    
      // 1
      func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        didReceive response: UNNotificationResponse,
        withCompletionHandler completionHandler: @escaping () -> Void
      ) {
        // 2
        if response.actionIdentifier == "markAsDone" {
          let userInfo = response.notification.request.content.userInfo
          if let taskData = userInfo["Task"] as? Data {
            if let task = try? JSONDecoder().decode(Task.self, from: taskData) {
              // 3
              TaskManager.shared.remove(task: task)
            }
          }
        }
        completionHandler()
      }
    }
    

    后记

    本篇主要讲述了关于本地通知的详细解析,感兴趣的给个赞或者关注~~~

    相关文章

      网友评论

          本文标题:系统推送的集成(二十二) —— 关于本地通知的详细解析(二)

          本文链接:https://www.haomeiwen.com/subject/nytejltx.html