版本记录
版本号 | 时间 |
---|---|
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()
}
}
后记
本篇主要讲述了关于本地通知的详细解析,感兴趣的给个赞或者关注~~~