[웹개발자의 IOS 탐방기] 8. SwiftUI 웹앱에 FCM Push 알림 구현하기

서론

Firebase Cloud Messaging (FCM)은 Google에서 제공하는 클라우드 기반의 메시징 솔루션이다. 이를 통해 개발자들은 Android, iOS, 웹 애플리케이션에 대한 푸시 알림을 보낼 수 있게 되었다. 본 포스팅에서는 간략하게 FCM에 대해 알아보고, IOS 앱에서 FCM Push 알림을 구현해보자.

 

 

FCM(Firebase Cloud Messaging) 기능

FCM은 앱 사용자와의 실시간 상호 작용을 가능하게 하여, 사용자 참여도를 높이고 중요한 정보를 신속하게 전달하는 데 효과적인 도구로 활용된다. 아래에 간략하게 FCM의 지원 기능 및 장점에 대해 요약해보았다.

  • 크로스 플랫폼 메시징: FCM은 Android, iOS, 웹 애플리케이션에 대한 푸시 알림을 지원하여, 하나의 서비스를 통해 다양한 플랫폼에 메시지를 보낼 수 있다.
  • 무료 및 무제한 사용: FCM은 무료로 제공되며, 메시지 전송에 대한 제한이 없다.
  • 알림 및 데이터 메시지: FCM을 통해 두 가지 유형의 메시지를 보낼 수 있다. 알림 메시지는 사용자의 디바이스에 알림을 표시하는 데 사용되며, 데이터 메시지는 앱의 백그라운드 처리나 데이터 전송에 사용된다.
  • 다양한 메시징 옵션: 개별 사용자, 사용자 그룹, 주제 구독자, 또는 장치 그룹에 대한 메시지를 보낼 수 있다.
  • 통합된 분석 기능: Firebase Analytics와의 통합을 통해 메시지 캠페인의 효과를 분석하고 사용자 참여를 높일 수 있다.
  • 고급 메시징 옵션: 메시지 우선순위 설정, 수명 설정, 조건부 메시징 등 고급 기능을 제공한다.

 

 

FCM Push 기능 구현하기

IOS 앱에 FCM Push 기능을 구현하기 위해서 나는 CocoaPods을 이용하여 FCM Push 기능 구현에 필요한 Firebase SDK 설치를 진행하였다.

 

1. FCM 공식 홈페이지에서 프로젝트 생성하기

 

2. apple앱에 filebase 추가 및 GoogleService-Info.plist 파일 프로젝트 폴더로 이동

 

3. cocoapods을 이용한 FCM SDK 설치

프로젝트 디렉토리에서 터미널을 열고 pod init을 실행하여 Podfile을 생성한다.

pod init

 

Podfile을 열고 Firebase 관련 패키지를 추가한다.

pod 'Firebase/Analytics'
pod 'Firebase/Messaging'

 

터미널에서 pod install을 실행하여 의존성을 설치한다.

pod install

 

4. AppDelegate에 Firebase 초기화및 푸쉬 알림 권한 요청 설정하기

AppDelegate에 Firebase 모듈을 import한 후 AppDelegate의 application(_:didFinishLaunchingWithOptions:) 메소드 안에 FirebaseApp.configure()를 추가한다. 그리고 사용자에게 푸시 알림을 보낼 수 있는 권한을 요청을 위해 AppDelegate의 UNUserNotificationCenter를 설정한다. xcode 13이상에서 SwiftUI 기반의 프로젝트를 구성한 경우, 별도의 AppDelegate.siwft 파일이 없다.  때문에 Main 위에 본인이 별도로 만든 AppDelegate를 import하거나 Delegate를 정의해주면 된다.


import UIKit
import UserNotifications

import FirebaseCore
import FirebaseMessaging


class AppDelegate: UIResponder, UIApplicationDelegate {
  var window: UIWindow?
  let gcmMessageIDKey = "gcm.message_id"

  func application(_ application: UIApplication,
                   didFinishLaunchingWithOptions launchOptions: [UIApplication
                     .LaunchOptionsKey: Any]?) -> Bool {
    FirebaseApp.configure()

    // [START set_messaging_delegate]
    Messaging.messaging().delegate = self
    // [END set_messaging_delegate]

    // Register for remote notifications. This shows a permission dialog on first run, to
    // show the dialog at a more appropriate time move this registration accordingly.
    // [START register_for_notifications]

    UNUserNotificationCenter.current().delegate = self

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

    application.registerForRemoteNotifications()

    // [END register_for_notifications]

    return true
  }

  func application(_ application: UIApplication,
                   didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
    // If you are receiving a notification message while your app is in the background,
    // this callback will not be fired till the user taps on the notification launching the application.
    // TODO: Handle data of notification

    // With swizzling disabled you must let Messaging know about the message, for Analytics
    // Messaging.messaging().appDidReceiveMessage(userInfo)

    // Print message ID.
    if let messageID = userInfo[gcmMessageIDKey] {
      print("Message ID: \(messageID)")
    }

    // Print full message.
    print(userInfo)
  }

  // [START receive_message]
  func application(_ application: UIApplication,
                   didReceiveRemoteNotification userInfo: [AnyHashable: Any]) async
    -> UIBackgroundFetchResult {
    // If you are receiving a notification message while your app is in the background,
    // this callback will not be fired till the user taps on the notification launching the application.
    // TODO: Handle data of notification

    // With swizzling disabled you must let Messaging know about the message, for Analytics
    // Messaging.messaging().appDidReceiveMessage(userInfo)

    // Print message ID.
    if let messageID = userInfo[gcmMessageIDKey] {
      print("Message ID: \(messageID)")
    }

    // Print full message.
    print(userInfo)

    return UIBackgroundFetchResult.newData
  }

  // [END receive_message]

  func application(_ application: UIApplication,
                   didFailToRegisterForRemoteNotificationsWithError error: Error) {
    print("Unable to register for remote notifications: \(error.localizedDescription)")
  }

  // This function is added here only for debugging purposes, and can be removed if swizzling is enabled.
  // If swizzling is disabled then this function must be implemented so that the APNs token can be paired to
  // the FCM registration token.
  func application(_ application: UIApplication,
                   didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    print("APNs token retrieved: \(deviceToken)")

    // With swizzling disabled you must set the APNs token here.
    // Messaging.messaging().apnsToken = deviceToken
  }
}

// [START ios_10_message_handling]

extension AppDelegate: UNUserNotificationCenterDelegate {
  // Receive displayed notifications for iOS 10 devices.
  func userNotificationCenter(_ center: UNUserNotificationCenter,
                              willPresent notification: UNNotification) async
    -> UNNotificationPresentationOptions {
    let userInfo = notification.request.content.userInfo

    // With swizzling disabled you must let Messaging know about the message, for Analytics
    // Messaging.messaging().appDidReceiveMessage(userInfo)

    // [START_EXCLUDE]
    // Print message ID.
    if let messageID = userInfo[gcmMessageIDKey] {
      print("Message ID: \(messageID)")
    }
    // [END_EXCLUDE]

    // Print full message.
    print(userInfo)

    // Change this to your preferred presentation option
    return [[.alert, .sound]]
  }

  func userNotificationCenter(_ center: UNUserNotificationCenter,
                              didReceive response: UNNotificationResponse) async {
    let userInfo = response.notification.request.content.userInfo

    // [START_EXCLUDE]
    // Print message ID.
    if let messageID = userInfo[gcmMessageIDKey] {
      print("Message ID: \(messageID)")
    }
    // [END_EXCLUDE]

    // With swizzling disabled you must let Messaging know about the message, for Analytics
    // Messaging.messaging().appDidReceiveMessage(userInfo)

    // Print full message.
    print(userInfo)
  }
}

// [END ios_10_message_handling]

extension AppDelegate: MessagingDelegate {
  // [START refresh_token]
  func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
    print("Firebase registration token: \(String(describing: fcmToken))")

    let dataDict: [String: String] = ["token": fcmToken ?? ""]
    NotificationCenter.default.post(
      name: Notification.Name("FCMToken"),
      object: nil,
      userInfo: dataDict
    )
    // TODO: If necessary send token to application server.
    // Note: This callback is fired at each app startup and whenever a new token is generated.
  }

  // [END refresh_token]
}

 

5. APNs Key 생성 및 FCM 인증 키 등록

Apple 개발자 센터에서 Apple Push Notifications service (APNs) 관련 Key를 생성한다.

FCM 프로젝트 설정창에 들어가서 나머지 설정을 완료해준다.

 

6. Build후 Firebase registration token 얻기

 

xcode 시뮬레이터에서의 FCM Push 알림 테스트는 2023.11.24일 본 포스팅 기준, 아직 지원되지 않는다. 때문에 테스트 관련 결과물은 회사 기능을 구현한 후에 첨부하겠다.