基于Firebase平台开发(九) —— 使用Firebase Cloud Messaging进行Push Notification的发送和接收(二)

版本记录

版本号 时间
V1.0 2021.04.10 星期六

前言

Firebase是一家实时后端数据库创业公司,它能帮助开发者很快的写出Web端和移动端的应用。自2014年10月Google收购Firebase以来,用户可以在更方便地使用Firebase的同时,结合Google的云服务。Firebase能让你的App从零到一。也就是说它可以帮助手机以及网页应用的开发者轻松构建App。通过Firebase背后负载的框架就可以简单地开发一个App,无需服务器以及基础设施。接下来几篇我们就一起看一下基于Firebase平台的开发。感兴趣的看下面几篇文章。
1. 基于Firebase平台开发(一) —— 基于ML Kit的iOS图片中文字的识别(一)
2. 基于Firebase平台开发(二) —— 基于ML Kit的iOS图片中文字的识别(二)
3. 基于Firebase平台开发(三) —— Firebase基本使用简介(一)
4. 基于Firebase平台开发(四) —— Firebase基本使用简介(二)
5. 基于Firebase平台开发(五) —— Firebase基本使用简介(三)
6. 基于Firebase平台开发(六) —— 基于Firebase Analytics的App使用率的跟踪(一)
7. 基于Firebase平台开发(七) —— iOS的A/B Test(一)
8. 基于Firebase平台开发(八) —— 使用Firebase Cloud Messaging进行Push Notification的发送和接收(一)

源码

1. Swift

首先看下工程组织结构

下面就是源码了

1. AppMain.swift
import SwiftUI

@main
struct AppMain: App {
  @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

  var body: some Scene {
    WindowGroup {
      AppTabView()
        .accentColor(Color("rw-green"))
    }
  }
}
2. AppDelegate.swift
import UIKit
import Firebase
import FirebaseMessaging
import FirebaseAnalytics

class AppDelegate: NSObject, UIApplicationDelegate {
  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
    FirebaseApp.configure()
    FirebaseConfiguration.shared.setLoggerLevel(.min)

    UNUserNotificationCenter.current().delegate = self
    let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
    UNUserNotificationCenter.current().requestAuthorization(
      options: authOptions) { _, _ in }
    application.registerForRemoteNotifications()

    Messaging.messaging().delegate = self

    return true
  }
}

extension AppDelegate: UNUserNotificationCenterDelegate {
  func userNotificationCenter(
    _ center: UNUserNotificationCenter,
    willPresent notification: UNNotification,
    withCompletionHandler completionHandler:
    @escaping (UNNotificationPresentationOptions) -> Void
  ) {
    process(notification)
    completionHandler([[.banner, .sound]])
  }

  func userNotificationCenter(
    _ center: UNUserNotificationCenter,
    didReceive response: UNNotificationResponse,
    withCompletionHandler completionHandler: @escaping () -> Void
  ) {
    process(response.notification)
    completionHandler()
  }

  func application(
    _ application: UIApplication,
    didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
  ) {
    Messaging.messaging().apnsToken = deviceToken
  }

  private func process(_ notification: UNNotification) {
    let userInfo = notification.request.content.userInfo
    UIApplication.shared.applicationIconBadgeNumber = 0
    if let newsTitle = userInfo["newsTitle"] as? String,
      let newsBody = userInfo["newsBody"] as? String {
      let newsItem = NewsItem(title: newsTitle, body: newsBody, date: Date())
      NewsModel.shared.add([newsItem])
      Analytics.logEvent("NEWS_ITEM_PROCESSED", parameters: nil)
    }
    Messaging.messaging().appDidReceiveMessage(userInfo)
    Analytics.logEvent("NOTIFICATION_PROCESSED", parameters: nil)
  }
}

extension AppDelegate: MessagingDelegate {
  func messaging(
    _ messaging: Messaging,
    didReceiveRegistrationToken fcmToken: String?
  ) {
    let tokenDict = ["token": fcmToken ?? ""]
    NotificationCenter.default.post(
      name: Notification.Name("FCMToken"),
      object: nil,
      userInfo: tokenDict)
  }
}
3. NewsModel.swift
import Foundation

class NewsModel: ObservableObject {
  @Published private(set) var newsItems: [NewsItem] = []

  func add(_ items: [NewsItem]) {
    var tempItems = newsItems
    tempItems.append(contentsOf: items)
    newsItems = tempItems.sorted {
      $0.date > $1.date
    }
  }
}

extension NewsModel {
  static let shared = mockModel()
  private static func mockModel() -> NewsModel {
    let newsModel = NewsModel()
    let decoder = JSONDecoder()
    decoder.dateDecodingStrategy = .iso8601
    guard
      let url = Bundle.main.url(forResource: "MockNewsItems", withExtension: "json"),
      let data = try? Data(contentsOf: url),
      let newsItems = try? decoder.decode([NewsItem].self, from: data)
    else {
      return newsModel
    }
    newsModel.add(newsItems)
    return newsModel
  }
}
4. NewsItem.swift
import Foundation

struct NewsItem: Identifiable, Codable {
  var id = UUID()
  let title: String
  let body: String
  let date: Date

  private enum CodingKeys: String, CodingKey {
    case title
    case body
    case date
  }
}
5. TopicsModel.swift
import SwiftUI
import FirebaseMessaging

class TopicsModel: ObservableObject {
  static let familyKey = "family"
  static let petsKey = "pets"
  @AppStorage(TopicsModel.familyKey) var family = false {
    didSet {
      updateSubscription(for: TopicsModel.familyKey, subscribed: family)
    }
  }
  @AppStorage(TopicsModel.petsKey) var pets = false {
    didSet {
      updateSubscription(for: TopicsModel.petsKey, subscribed: pets)
    }
  }

  private func updateSubscription(for topic: String, subscribed: Bool) {
    if subscribed {
      subscribe(to: topic)
    } else {
      unsubscribe(from: topic)
    }
  }

  private func subscribe(to topic: String) {
    Messaging.messaging().subscribe(toTopic: topic)
  }

  private func unsubscribe(from topic: String) {
    Messaging.messaging().unsubscribe(fromTopic: topic)
  }
}
6. AppTabView.swift
import SwiftUI

struct AppTabView: View {
  var body: some View {
    TabView {
      NavigationView {
        NewsListView(newsModel: NewsModel.shared)
      }
      .tabItem {
        Image(systemName: "newspaper")
        Text("News")
      }
      NavigationView {
        TopicsView()
      }
      .tabItem {
        Image(systemName: "rectangle.3.offgrid.bubble.left")
        Text("Topics")
      }
    }
  }
}

struct AppTabView_Previews: PreviewProvider {
  static var previews: some View {
    AppTabView()
  }
}
7. NewsListView.swift
import SwiftUI

struct NewsListView: View {
  @ObservedObject var newsModel: NewsModel

  var body: some View {
    List(newsModel.newsItems) { newsItem in
      NewsItemView(newsItem: newsItem)
    }
    .listStyle(InsetGroupedListStyle())
    .navigationTitle("Good News")
  }
}

struct NewsListView_Previews: PreviewProvider {
  static var previews: some View {
    NavigationView {
      NewsListView(newsModel: NewsModel.shared)
    }
  }
}
8. NewsItemView.swift
import SwiftUI

struct NewsItemView: View {
  let newsItem: NewsItem
  var body: some View {
    VStack(alignment: .leading) {
      Text(newsItem.title)
        .font(.title2)
        .bold()
      Text(newsItem.body)
        .font(.body)
      Text(newsItem.date, formatter: dateFormatter)
        .font(.caption2)
        .italic()
    }
  }

  private var dateFormatter: DateFormatter {
    let formatter = DateFormatter()
    formatter.dateStyle = .short
    formatter.timeStyle = .short
    return formatter
  }
}

struct NewsItemRowView_Previews: PreviewProvider {
  static var previews: some View {
    NewsItemView(newsItem: NewsItem(title: "News Title", body: "News body, with more information", date: Date()))
  }
}
9. TopicsView.swift
import SwiftUI

struct TopicsView: View {
  @ObservedObject var topicsModel = TopicsModel()

  var body: some View {
    Form {
      Section(header: Text("Select the topics you would like to receive notifications for")) {
        Toggle("Family", isOn: $topicsModel.family)
        Toggle("Pets", isOn: $topicsModel.pets)
      }
    }
    .navigationTitle("Topics")
  }
}

struct SubscriptionsView_Previews: PreviewProvider {
  static var previews: some View {
    NavigationView {
      TopicsView()
    }
  }
}
10. NotificationService.swift
import UserNotifications
import FirebaseMessaging

class NotificationService: UNNotificationServiceExtension {
  var contentHandler: ((UNNotificationContent) -> Void)?
  var bestAttemptContent: UNMutableNotificationContent?

  override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
    self.contentHandler = contentHandler
    bestAttemptContent = request.content.mutableCopy() as? UNMutableNotificationContent
    guard let bestAttemptContent = bestAttemptContent else { return }
    FIRMessagingExtensionHelper().populateNotificationContent(
      bestAttemptContent,
      withContentHandler: contentHandler)
  }

  override func serviceExtensionTimeWillExpire() {
    // Called just before the extension will be terminated by the system.
    // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
    if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
      contentHandler(bestAttemptContent)
    }
  }
}

后记

本篇主要讲述了使用Firebase Cloud Messaging进行Push Notification的发送和接收,感兴趣的给个赞或者关注~~~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,907评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,546评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,705评论 0 238
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,624评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,940评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,371评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,672评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,396评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,069评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,350评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,876评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,243评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,847评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,004评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,755评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,378评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,266评论 2 259

推荐阅读更多精彩内容