Jihyun Kim
[Swift] IOS에서 이미지가 있는 푸시알림 구현하기 - APNs, FCM, Notification Service Extension 본문
Swift
[Swift] IOS에서 이미지가 있는 푸시알림 구현하기 - APNs, FCM, Notification Service Extension
hyunns 2023. 4. 11. 15:12푸시알림 동작 방식
앱에서 푸시알림을 받기 위해서는, Push Server (Provider)와 Device Token이 필요하다.
IOS에서는 device token을 APNs에 요청하면 된다. (APNs는 Apple Push Notification Service의 약자로, 애플이 개발한 플랫폼 알림 서비스이다.) 이후 APNs에서 받은 device token을 Push Server에 넘긴다. 그러면 서버는 APNs에 푸시알림을 보낼 데이터들을 전달한다. 그럼 앱애서는 APNs에 있는 데이터를 받아서 보여주면 된다.
좀 더 이해하기 쉽게 정리해봤다.
Device Token 요청 (FCM)
- 안드로이드와 함께 개발했기 때문에, Google Firebase에서 제공해주는 FCM Token을 사용하였다.
https://firebase.google.com/docs/cloud-messaging/ios/client?hl=ko
FCM 요청 코드
// 앱델리게이트
extension AppDelegate: MessagingDelegate {
// APN 토큰을 FCM 등록 토큰에 매핑
func application(application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
Messaging.messaging().apnsToken = deviceToken
}
// 토큰 갱신 모니터링
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.
}
}
푸시 알림 띄우기
푸시 알림 받는 코드
// 앱델리게이트
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification) async
-> UNNotificationPresentationOptions {
// 푸시 알림 데이터가 userInfo에 담겨있다.
let userInfo = notification.request.content.userInfo
print(userInfo)
// 푸시 알림 옵션 반환
return [[.banner, .list, .sound]]
}
// 앱이 백그라운드 상태일때 원격 알림 수신
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) async -> UIBackgroundFetchResult {
print(userInfo)
return UIBackgroundFetchResult.newData
}
}
푸시알림에 이미지 넣기
우선, 이미지를 넣기 위해서는 Notification Service Extension을 설치해야 한다.
Notification Service Extension의 코드
import UserNotifications
import FirebaseMessaging
class NotificationService: UNNotificationServiceExtension {
// 원격 푸시 알림 수신하는 곳이다.
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
if let bestAttemptContent = bestAttemptContent {
// 수신한 데이터는 userInfo에 들어있으며,
// 키값을 활용하여 이미지 데이터를 꺼내 써야한다.
let apsData = request.content.userInfo["aps"] as! [String : Any]
let imageData = request.content.userInfo["fcm_options"] as! [String : Any]
// 받은 이미지 데이터는 string 형태로 이미지가 있는 주소 값이다.
guard let urlImageString = imageData["image"] as? String else {
contentHandler(bestAttemptContent)
return
}
if let imageUrl = URL(string: "\(urlImageString)") {
guard let imageData = try? Data(contentsOf: imageUrl) else {
contentHandler(bestAttemptContent)
return
}
// url을 임시로 저장한 후 저장된 파일 경로를 가져온다.
guard let attachment = UNNotificationAttachment.saveImageToDisk(identifier: "image.jpg", data: imageData, options: nil) else {
contentHandler(bestAttemptContent)
return
}
bestAttemptContent.attachments = [ attachment ]
}
// Firebase Messaging로 이미지 넣기
Messaging.serviceExtension().populateNotificationContent(bestAttemptContent, withContentHandler: self.contentHandler!)
contentHandler(bestAttemptContent)
}
}
}
// 임시로 이미지 파일 저장하는 extension
extension UNNotificationAttachment {
static func saveImageToDisk(identifier: String, data: Data, options: [AnyHashable : Any]? = nil) -> UNNotificationAttachment? {
let fileManager = FileManager.default
let folderName = ProcessInfo.processInfo.globallyUniqueString
let folderURL = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(folderName, isDirectory: true)!
do {
try fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil)
let fileURL = folderURL.appendingPathExtension(identifier)
try data.write(to: fileURL)
let attachment = try UNNotificationAttachment(identifier: identifier, url: fileURL, options: options)
return attachment
} catch {
print("saveImageToDisk error - \(error)")
}
return nil
}
}