본문 바로가기
앱/IOS(애플)

IOS FCM 푸시 알림 연동(Spring)

by redbear0077 2025. 1. 3.
반응형

IOS FCM 푸시 알림 연동(Spring)

 

환경

IOS
스프링 부트
웹뷰

 

주의

작성한 코드는 수정이 필요한 코드이다 동일한 코드 작성 시 정상적인 작동을 하지 않는다.

 

알림 과정
앱 > 인증번호 요청 > 서버(스프링 부트) > 인증번호 생성 > 앱 알림(푸시)

 

FCM 푸시 동작 과정

출처 : https://firebase.google.com/docs/cloud-messaging/fcm-architecture?hl=ko

 

1. FCM 프로젝트 생성

 

  • 프로젝트 만들기

 

  • 프로젝트 이름 생성(자신이 구분할 수 있는 이름)

 

  • 애널리틱스는 미사용으로 진행(사용 시 별도설명확인) 

 

  • 프로젝트 생성 완료(다소 시간이 걸릴수 있다)

 

2. 앱에 Firebase에 추가
  • IOS선택

 

  • 앱 등록
xcode프로젝트 생성 방법
Apple번들 ID만 필수고 나머지는 선택사항이다

 

  • Apple번들 ID 확인방법

  • 구성 파일 다운로드
파일 위치 : 해당 프로젝트의 /ROOT/Test(프로젝트 명) 경로에 두어야 한다.

 

  • 파일 적용 화면

  • Firebase SDK 추가
cocoapods사용하여 SDK추가
추가방법 및 cocoapods사용방법

 

  • 초기화 코드 추가
사용하는 코드로 복사

  • 수정한 코드
import UIKit
import Firebase
import UserNotifications

@main
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, MessagingDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Firebase 초기화
        FirebaseApp.configure()
        
        // FCM 메시지 수신 대리자 설정
        Messaging.messaging().delegate = self
        
        // 푸시 알림 권한 요청
        requestNotificationAuthorization(application)
        
        return true
    }
    
    // 알림 권한 요청 메서드
    func requestNotificationAuthorization(_ application: UIApplication) {
        let center = UNUserNotificationCenter.current()
        center.delegate = self
        center.requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
            if granted {
                DispatchQueue.main.async {
                    application.registerForRemoteNotifications()
                }
            }
        }
    }
    
    // APNs 토큰 등록
    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        let tokenParts = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
        print("Device Token: \(tokenParts)")
        
        // FCM 토큰 등록
        Messaging.messaging().apnsToken = deviceToken
    }
    
    func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
        print("Failed to register for remote notifications: \(error.localizedDescription)")
    }
    
    // MARK: MessagingDelegate
    func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
        if let fcmToken = fcmToken {
            print("FCM Token: \(fcmToken)")
        }
    }
    
    // MARK: UNUserNotificationCenterDelegate
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        completionHandler([.alert, .badge, .sound])  // 앱이 활성화된 동안에도 알림을 표시
    }
    
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        // 알림 탭 후 행동 처리
        print("User tapped the notification")
        completionHandler()
    }
}

 

  • 설정 완료

 

3. 프로젝트 설정
  • 프로젝트 정보 및 info.plist 파일 확인가능

 

  • Apple APN인증기 설정

 

  • APN 인증 키 발급
발급 방법 링크

  • APNs 인증 키 업로드
APNs인증 키 : 발급 방법 링크에서 받은 Authkey_xxxxxxxxxx.p8
키 ID : 발급받은 키의 xxxxxxxxxx에 해당하는 내용
팀 ID : 멤버십 세부 사항에 있는 팀 ID

  • 팀 ID

 

  • APNs 인증 키 등록 완료

 

4. Xcode 설정
  • Background Modes, Push Notifications 설정
Automatically manage signing 체크

 

  • Background Modes 추가

 

  • Remote notifications체크
이 옵션은 앱이 Apple Push Notification Service(APNs)에서 푸시 알림을 수신했을 때 앱이 백그라운드에서 특정 작업을 수행할 수 있다

활설화 필요한 경우
백그라운드 데이터 동기화 : 서버에서 새로운 데이터를 푸시 알림으로 알려주고, 앱이 데이터를 자동으로 동기화
예 : 뉴스 앱에서 새 기사가 추가 될 때

알림 데이터 처리 : 사용자가 알림을 클릭하기 전에 앱이 알림 데이터를 미리 처리
예 : 채팅 앱에서 알림 수신 시 메시지를 미리 로드

푸시 알림 기반 기능
예 : 특정 이벤트가 발생했을 때 알림을 받고 백그라운드에서 작업 수행

  • Push Notifications 추가

 

  • 푸시 알림(Cloud Messaging) 테스트

 

  • 캠페인 만들기

 

  • 알림 선택

 

  • 테스트 메시지 작성

 

  • 토큰값 확인

 

  • 전송

 

  • 받은 알림

 

5. 자바 코드 설정
  • 자바코드 작성 준비(새 비공개 키 생성은 자바코드에 필요한 키)

  • 코드 수정(getFile(받아온 파일 위치작성)
@Configuration
public class FcmConfig {

    @Bean
    public FirebaseApp firebaseApp() throws Exception {
        List<FirebaseApp> apps = FirebaseApp.getApps();
        if (!apps.isEmpty()) {
            // 이미 존재하는 FirebaseApp 인스턴스가 있는 경우, 첫 번째 인스턴스를 반환
            return apps.get(0);
        }

        File configFile = ResourceUtils.getFile("classpath:fcm/config/wonthelotto-app.json");
        FileInputStream serviceAccount = new FileInputStream(configFile);

        FirebaseOptions options = FirebaseOptions.builder()
                .setCredentials(GoogleCredentials.fromStream(serviceAccount))
                .build();

        return FirebaseApp.initializeApp(options);
    }
}

 

  • 전송을 위한 코드(전후 기능은 별도 작성필요)
Message message = Message.builder()
                .setToken(XCODE에서 받은 토큰)
                .setNotification(
                        Notification.builder()
                        .setTitle("FCM TEST")
                        .setBody("테스트")
                        .build()
                )
                .putData("verificationCode", "테스트")
                .build();

FirebaseMessaging.getInstance().send(message);

 

  • IOS코드 수정
자바에서 전송(.send(message);를 두 번 하는 경우 알림이 두 번 올 수 있다.
import UIKit
import Firebase


@main
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, MessagingDelegate {

    var window: UIWindow?
    let SERVER_IP = "전송하는 URL"
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        FirebaseApp.configure()
        
        // 알림 센터 델리게이트 설정
        UNUserNotificationCenter.current().delegate = self
        
        // 푸시 알림 권한 요청
        let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
        UNUserNotificationCenter.current().requestAuthorization(options: authOptions) { granted, error in
            if let error = error {
                print("알림 권한 요청 실패: \(error.localizedDescription)")
            } else {
                print("알림 권한: \(granted ? "허용됨" : "거부됨")")
            }
        }

        // 원격 알림 등록
        application.registerForRemoteNotifications()
        
        // Firebase Messaging 델리게이트 설정
        Messaging.messaging().delegate = self
        
        return true
    }
    
    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        // APNs 토큰 등록
        Messaging.messaging().apnsToken = deviceToken
        print("APNs 토큰 등록 완료: \(deviceToken.map { String(format: "%02.2hhx", $0) }.joined())")
    }

    func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
        print("APNs 등록 실패: \(error.localizedDescription)")
    }
    
    func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
        // FCM 토큰 수신 및 서버 전송
        if let fcmToken = fcmToken {
            let deviceId = UIDevice.current.identifierForVendor?.uuidString ?? "UnknownDevice"
            print("FCM 토큰: \(fcmToken)")
            sendTokenToServer(deviceId: deviceId, token: fcmToken)
        }
    }

    func sendTokenToServer(deviceId: String, token: String) {
        // FCM 토큰 서버로 전송
        guard let url = URL(string: "\(SERVER_IP)/나머지 URL") else {
            print("유효하지 않은 URL")
            return
        }
        
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        
        let body: [String: String] = ["deviceId": deviceId, "token": token]
        guard let httpBody = try? JSONSerialization.data(withJSONObject: body, options: []) else {
            print("HTTP Body 생성 실패")
            return
        }
        request.httpBody = httpBody
        
        let task = URLSession.shared.dataTask(with: request) { data, response, error in
            if let error = error {
                print("FCM 토큰 전송 실패: \(error.localizedDescription)")
                return
            }
            
            if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 {
                print("Token sent successfully")
            } else {
                print("Failed to send token. Response code: \((response as? HTTPURLResponse)?.statusCode ?? -1)")
            }
        }
        task.resume()
    }

    // MARK: UNUserNotificationCenterDelegate
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        let userInfo = notification.request.content.userInfo
        print("알림 수신 (포그라운드): \(userInfo)")
        
        // 메시지 고유 ID 확인
        if let messageId = userInfo["gcm.message_id"] as? String {
            print("메시지 ID: \(messageId)")
        }
        
        completionHandler([.alert, .badge, .sound])
    }


    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        let userInfo = response.notification.request.content.userInfo
        print("알림 수신 (클릭): \(userInfo)")
        completionHandler()
    }

}
반응형

' > IOS(애플)' 카테고리의 다른 글

[IOS]APN 인증키(.p8) 발급 및 푸시 알림 설정  (0) 2025.01.03
IOS XCODE 프로젝트 생성  (0) 2025.01.03