푸쉬 토큰을 전달 받은 Provider는 추후 디바이스에 푸쉬를 전송할 대 APNs에 요청을 하고 APNs가 디바이스에 푸쉬를 전송함
Remote Notification 구현하기
1. 실제 iOS기기 준비
시뮬레이터로 테스트 불가능했지만 Xcode 11.4부터 시뮬레이터에서 푸쉬를 수신할 수 있는 기능을 지원하기 시작했음
2. 애플 개발자 사이트 설정하기
App Identifier 등록하기
Bundle Identifier 및 Description 추가하기
Capabilities -> Push Notification On
3. Apple push 알림 인증 키 발급받기
유료 애플 개발자 프로그램을 등록해야 푸시 키를 발급받을 수 있음
푸시 인증 키는 여러 앱에서 동시에 사용이 가능함
p8 인증키를 발급 받음
4. Xcode의 App > Capabilities(기능)에서 푸시 알림 사용 설정
Debug/Release Mode 모두 설정해주어야 함
Push Notification
Background Modes -> Remote Notification On
5. Firebase Cloud System 연동
Firebases는 푸쉬를 Cloud Messaging Service라고 지칭하고 있음
푸시 인증키 및 개발자 팀 키 등록
프로젝트 설정 -> 클라우드 메시징 탭 - > APN 인증 키 업로드
6. 코드 구현
원격 알림 권한을 등록해야 함
iOS 시스템 정책으로 원격 알림 시스템에 앱 등록하는 과정이 필요함
파이어베이스도 푸쉬를 전송할 때 APNs 서버에 요청하게 됨으로 실질적 발송 및 관리자는 애플 시스템의 영향을 받음
UNUserNotificationCenterDelegate
메시지 대리자 설정
파이어베이스가 푸시 메시지를 대신 전송할 수 있도록 대리자를 설정하는 과정이 필요
MessagingDelegate
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
UIViewController.swizzleMethod()
FirebaseApp.configure()
//알림 시스템에 앱을 등록
if #available(iOS 10.0, *) {
// For iOS 10 display notification (sent via APNS)
UNUserNotificationCenter.current().delegate = self
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(
options: authOptions,
completionHandler: { _, _ in }
)
} else {
let settings: UIUserNotificationSettings =
UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
application.registerUserNotificationSettings(settings)
}
application.registerForRemoteNotifications()
//메시지 대리자 설정
Messaging.messaging().delegate = self
//현재 등록된 토큰 가져오기
Messaging.messaging().token { token, error in
if let error = error {
print("Error fetching FCM registration token: \(error)")
} else if let token = token {
print("FCM registration token: \(token)")
}
}
return true
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
}
extension AppDelegate: UNUserNotificationCenterDelegate {
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
Messaging.messaging().apnsToken = deviceToken
}
//포그라운드 알람 수신: 로컬/푸쉬 동일
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
//.banner, .list: ios 14+
guard let viewController = (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.window?.rootViewController?.topViewController else { return }
if viewController is SettingViewController {
} else {
completionHandler([.badge, .sound, .banner, .list])
}
}
//푸시 클릭: 호두과자 장바구니 담는 화면
//유저가 푸시를 클릭했을 때에만 수신 확인 가능
//특정 푸시를 클릭하면 특정 상세화면으로 화면 전환 ->
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
print("사용자가 푸쉬를 클릭했습니다.")
print(response.notification.request.content.body)
print(response.notification.request.content.userInfo)
let userInfo = response.notification.request.content.userInfo
if userInfo[AnyHashable("sesac2")] as? String == "project2" {
print("Sesac PROJECT")
} else {
print("EMPTY")
}
guard let viewController = (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.window?.rootViewController?.topViewController else { return }
print(viewController)
if viewController is ViewController {
viewController.navigationController?.pushViewController(SettingViewController(), animated: true)
} else if viewController is ProfileViewController {
viewController.dismiss(animated: true)
}
}
}
extension AppDelegate: MessagingDelegate {
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.
}
}
7. 테스트 메시지 전송
콘솔에서 토큰 확인 후 파이어베이스에서 테스트 후 전송
추가 사항
Foreground
출시 앱 프로젝트를 할 때도 겪었던 이슈 백그라운에서는 기본적으로 동작하지만 포그라운드 수신은 동작하지 않음