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 

 

Apple 플랫폼에서 Firebase 클라우드 메시징 클라이언트 앱 설정

5월 10일, Google I/O에서 Firebase가 돌아옵니다. 지금 등록하기 의견 보내기 Apple 플랫폼에서 Firebase 클라우드 메시징 클라이언트 앱 설정 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠

firebase.google.com

 

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
	}
}

 

 

푸시알림 구현 결과