Skip to main content

iOS 가이드

일반 캠페인 메시지 발송을 위해서는 Apple 프로젝트에 Firebase를 추가해야 합니다. 자세한 내용은 다음 링크를 참고해주세요.

APNs 연동

APNs 연동을 위해서는 애플 개발자 웹사이트에서 인증서를 발급받은 뒤 파이어베이스 콘솔에 등록해야 합니다.

1. 애플 개발자 포털 - 계정 메뉴 - ID 및 프로파일의 키 메뉴를 클릭합니다.

2. + 버튼을 눌러 키값을 추가합니다.

3. 키 이름을 입력하고 Apple Push Notifications service (APNs)를 체크합니다.

4. Register 버튼을 눌러 키값을 등록합니다.

5. Register 버튼을 눌러 인증키값을 발급받습니다.

발급받은 인증 키는 다운로드 후 안전한 장소에 보관합니다. AuthKey_KeyID.p8 형식으로 파일명이 구성되어 있습니다.

6. 해당 파일을 파이어베이스 콘솔 - 인증 키를 업로드할 프로젝트 - 설정 - 클라우드 메시징 탭에 업로드합니다.

클라우드 메시징 탭을 클릭합니다.

업로드 버튼을 눌러 APNs 인증 키를 업로드합니다.

업로드가 완료된 화면

키 아이디는 p8 인증서 발급 시 생성된 Key ID를 입력하면 되며, 팀 ID는 애플 개발자 계정의 멤버십 세부사항에 있는 팀 ID값을 입력하면 됩니다.

Xcode 설정

프로젝트에 FirebaseMessaging 의존성을 추가하고 앱을 초기화합니다. 파이어베이스 의존성을 추가하고 초기화 하는 방법에 대한 자세한 내용은 다음 링크를 참고해주세요.

이후 Xcode에서 Signing & Capabilities 탭에서 Capability 를 탭한 뒤 Push NotificationsBackground Modes를 추가합니다.

1-1. 좌측 상단의 Capabilities를 클릭합니다.

1-2. Background Modes를 체크합니다.

백그라운드 모드를 추가한 뒤 Capabilities의 Background Modes, Push Notifications 추가, Remote notifications 를 체크합니다.

프로젝트에 Firebase 세팅하기

1. Firebase 인스턴스 초기화

프로젝트에서 FirebaseApp.configure() 함수를 호출하여 파이어베이스 인스턴스의 초기 설정을 진행합니다.

FirebaseApp.configure() 함수 호출 시점에 주의해주세요!

FirebaseApp.configure() 함수 호출은 앱 런치 이후 시점과 파이어베이스 기능들을 사용하기 전 시점 사이에 이루어져야 합니다.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.

FirebaseApp.configure()
// ...
return true
}

2. FCM 토큰 전달

일반 캠페인 메시지 연동을 위해 로플랫 서버로 fcm 토큰을 전달해주셔야 합니다. 아래 절차를 따라주세요.

2-1. FirebaseCore와 FirebaseMessaging을 import하고, AppDelegate 클래스에 MessagingDelegate 프로토콜을 채택합니다.

import UIKit
import FirebaseCore // 추가
import FirebaseMessaging // 추가

@main
class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate {
// ...
}

2-2. AppDelegate 클래스 내에서 FCM 토큰을 발급받습니다.

  1. 파이어베이스의 Messaging 인스턴스 델리게이트를 AppDelegate 인스턴스로 지정합니다.
  2. 파이어베이스에서 발급받은 fcm 토큰을 Plengi.registerFcm 함수 인자로 넘겨주시면 됩니다.
토큰값 갱신

토큰이 갱신되면 동일한 함수를 통해 갱신된 토큰 값이 자동으로 전달됩니다.

warning

sdk에서 FCM 토큰과 로플랫 서버로부터 할당된 FCM 토큰 아이디 값을 UserDefaults 에 저장하기 위한 키값은 loplat_fcm_tokenloplat_fcm_token_id 입니다. 앱 내부 정책 상 UserDefaults키값이 충돌나지 않는지 확인 부탁드립니다.

// 1. AppDelegate에 MessagingDelegate를 채택합니다.
@main
class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate, UNUserNotificationCenterDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

FirebaseApp.configure()

// 2. Messaging 델리게이트를 AppDelegate로 지정합니다.
Messaging.messaging().delegate = self
}

// 3. 아래 함수를 통해 fcm 토큰이 발급되면 'registerFcm'으로 SDK에 토큰을 전달해주세요.
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
Plengi.registerFcm(fcmToken: fcmToken)
}
}
info

SwiftUI 기반의 앱이거나 메서드 재구성을 비활성화 한 경우 파이어베이스 SDK에 디바이스 토큰을 명시적으로 전달해야 합니다.

@main
class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate, UNUserNotificationCenterDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
Messaging.messaging().delegate = self
}

func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
Plengi.registerFcm(fcmToken: fcmToken)
}

// deviceToken을 파이어베이스에 전달합니다.
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
Messaging.messaging().apnsToken = deviceToken
}
}

3. 사용자에게 알람 권한 요청

사용자에게 권한을 요청하기 위해 AppDelegate 클래스에 UNUserNotificationCenterDelegate 프로토콜을 채택합니다. 푸시 알람의 형태는 앱 내부 정책에 따라 지정해줍니다.

(예시 - badge 형태 푸시만 생성할 경우 .badge 값만 옵션으로 지정합니다.)

위의 옵션을 NotificationCenter.current().requestAuthorization(options:) 함수 파라미터로 전달하여 사용자에게 푸시 알람 생성 권한을 요청합니다.

권한이 허용되면 registerForRemoteNotifications 함수를 호출하여 기기의 푸시 알람 기능을 활성화합니다.

import FirebaseCore
import FirebaseMessaging

// UNUserNotificationCenterDelegate 프로토콜을 채택합니다.
class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate, UNUserNotificationCenterDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.

FirebaseApp.configure()
Messaging.messaging().delegate = self

// 노티피케이션 센터 델리게이트를 AppDelegate로 지정합니다.
UNUserNotificationCenter.current().delegate = self

// 요청할 알람의 옵션을 지정합니다.
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]

// 사용자에게 권한을 요청합니다.
UNUserNotificationCenter.current().requestAuthorization(options: authOptions) { (granted, error) in
guard granted else { return }
DispatchQueue.main.async {
application.registerForRemoteNotifications()
}
}

return true
}
}

4. echo code 등록

"echo_code는 loplat SDK를 start하기 전에 반드시 등록하셔야 합니다."
  • echo_code는 로플랫과 데이터 연동시 고객사에서 분석과 조회하기 위한 사용자 식별 코드입니다. 이 코드는 오직 고객사에 전달하는 용도로만 사용되며 별도 저장하거나 활용하지 않습니다.
  • echo_code가 등록되지 않은 상태에서 알림을 수신하면 성과 측정 시 결과가 반영되지 않을 수 있습니다.

AppDelegate 클래스에 PlaceDelegate 프로토콜을 채택합니다.

class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate, UNUserNotificationCenterDelegate, PlaceDelegate {

이후, AppDelegate 클래스에 실제 SDK를 초기화하는 코드를 추가합니다.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [IOApplicationLaunchOptionsKey: Any]?) -> Bool {
// ********** 중간 생략 ********** //
if Plengi.initialize(clientID: "로플랫에서 발급받은 클라이언트 아이디", clientSecret: "로플랫에서 발급받은 클라이언트 키") == .SUCCESS) {
// init 성공
// 필요 시 호출
Plengi.setEchoCode(echoCode: "고객사 별 사용자를 식별할 수 있는 코드 (개인정보 주의바람)")
} else {
// init 실패
}
// ********** 중간 생략 ********** //
}
"Plengi.initialize() 함수는 반드시 AppDelegate 안의 application(_:didFinishLaunchingWithOptions:) 에서 호출되어야 합니다."

다른 곳에서 호출할 경우, SDK가 작동하지 않습니다.

echoCode 에는 개인정보가 포함되면 안됩니다.

이메일, 전화번호와 같은 개인정보 혹은 광고ID(ADID, IDFA)를 전달하지 마세요.

5. SDK 구동하기

자사 앱의 마케팅 알림 수신 동의 여부에 따라 enableAdNetwork를 호출해주세요.

enableAdNetwork 함수의 enableAdtrue 값으로 설정됨과 동시에 일반 마케팅 메시지 수신이 시작됩니다.

FCM 토큰이 저장된 이후 시점에 enableAdtrue로 설정하도록 함수 호출 순서에 유의해주세요.

registerFCM 함수 호출을 가이드에 따라 진행하였을 때, 일반적인 경우 앱 실행 직후 FCM 토큰이 sdk에 저장됩니다.

앱 첫 설치 이후 너무 이른 시기에 enableAd값을 true로 설정하지 않도록 주의해주세요.

마케팅 알림 설정이 On인 경우

SDK가 로플랫 서버에 수신 가능 여부를 알리고, 일반 마케팅 메시지를 수신하기 시작합니다.

[Plengi enableAdNetwork:YES];

마케팅 알림 설정이 Off인 경우

SDK가 로플랫 서버에 수신 중단을 알리고, 이후로는 메시지를 전송하지 않습니다.

[Plengi enableAdNetwork:NO];

6. 캠페인 통계 수집

캠페인 통계 수집을 위해서는 푸시 클릭률을 트래킹하도록 SDK에 정보를 보내주셔야 합니다.

AppDelegate 클래스 선언부에 UNUserNotificationCenterDelegate 프로토콜을 채택한 뒤 아래 메서드를 구현합니다.

extension AppDelegate: UNUserNotificationCenterDelegate {

func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
// 아래 함수를 호출해주세요.
_ = Plengi.processLoplatAdvertisement(center, didReceive: response, withCompletionHandler: completionHandler)

completionHandler()
}
}

위 형태로 함수를 호출해주시면 일반 캠페인에 대한 dismiss 이벤트와 click 이벤트를 수집하게 됩니다.

7. (Advanced) 딥링크 이동

일반 캠페인 푸시 메시지는 클릭 시 딥링크 이동을 지원합니다. 푸시 메시지를 통해 연결해둔 앱이 열리는 경우 아래의 설정을 통해 열린 딥링크 정보를 확인할수 있습니다.

class AppDelegate: NSObject, UIApplicationDelegate {
...
func application(_ application: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:] ) -> Bool {
// 오픈된 url에 대한 처리를 해주세요.
print("\(url.absoluteString) opened.")
}
...
}

iOS 딥링크에 대한 자세한 사항은 iOS 딥링크 가이드 에서 확인 가능합니다.

8. (Advanced) 이미지를 포함한 푸시 알림 (Rich Notification)

일반 캠페인에는 페이로드의 image_uri를 활용하여 이미지를 포함한 푸시 알림을 표시할 수 있습니다.

  1. Xcode에 Notification Service Extension을 추가합니다. 프로젝트 설정 파일 좌측 하단의 + 버튼을 클릭합니다.

  1. Notification Service Extension을 검색한 뒤 선택하고 Next 버튼을 클릭합니다.

  1. 이름을 작성하고, 프로젝트와 Embed in Application은 현재 개발중인 애플리케이션으로 설정한 뒤 Finish를 클릭합니다.

아래는 추가가 완료된 화면입니다.

Notification Service Extension이 제대로 동작하지 않을 때

Notification Service Extension 타겟의 Minimum Deployments 버전 값과 동일하게 설정되어 있는지 확인해주세요. 그렇지 않은 경우 제대로 동작하지 않을 수 있습니다.

  1. NotificationService 클래스에 코드를 작성합니다.

Notification Service Extension이 추가되면 파일 목록에 익스텐션 추가 시 작성했던 productName을 이름으로 하여 그룹 내에 파일들이 생성되어 있을 것입니다.

그룹 내의 NotificationService 파일을 선택합니다.

아래 코드를 해당 파일에 복사한 뒤 붙여넣습니다.

클라이언트 아이디 값을 반드시 입력해주세요!

아래 코드를 붙여넣어 주신 뒤, 반드시 [CLIENT_ID]에 클라이언트 아이디 값을 직접 입력해주세요. 올바른 클라이언트 아이디 값을 입력해주셔야 푸시 알림에 이미지를 삽입하는 코드가 동작하게 됩니다.

// [CLIENT_ID]에 클라이언트 아이디 값을 직접 입력해주세요.
if bestAttemptContent.userInfo["client_id"] as? String == [CLIENT_ID] {
// ...
}
import UserNotifications

class NotificationService: UNNotificationServiceExtension {

var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?

override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)

if let bestAttemptContent = bestAttemptContent {
// Modify the notification content here...
// 새로 추가된 코드
if bestAttemptContent.userInfo["client_id"] as? String == "cashplace" {
if let urlString = bestAttemptContent.userInfo["image_uri"] as? String,
let url = URL(string: urlString),
let imageData = NSData(contentsOf: url),
let attachment = UNNotificationAttachment.create(
imageFileIdentifier: "loplatAdvertisement.jpg",
data: imageData,
options: nil
) {
if let index = bestAttemptContent.attachments.firstIndex(where: {
$0.identifier == "loplatAdvertisement.jpg"
}) {
bestAttemptContent.attachments.remove(at: index)
}
bestAttemptContent.attachments.append(attachment)
}
}

contentHandler(bestAttemptContent)
}
}

override func serviceExtensionTimeWillExpire() {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
}

@available(iOS 10.0, *)
extension UNNotificationAttachment {
static func create(imageFileIdentifier: String, data: NSData, options: [NSObject: AnyObject]?) -> UNNotificationAttachment? {
let fileManager = FileManager.default
let tmpSubFolderName = ProcessInfo.processInfo.globallyUniqueString
let fileURLPath = NSURL(fileURLWithPath: NSTemporaryDirectory())

guard let tmpSubFolderURL = fileURLPath.appendingPathComponent(tmpSubFolderName, isDirectory: true) else {
return nil
}

do {
try fileManager.createDirectory(at: tmpSubFolderURL, withIntermediateDirectories: true, attributes: nil)
let fileURL = tmpSubFolderURL.appendingPathComponent(imageFileIdentifier)
try data.write(to: fileURL, options: [])
return try UNNotificationAttachment.init(identifier: imageFileIdentifier, url: fileURL, options: options)
} catch {
return nil
}
}
}
warning

다운로드 하려는 이미지의 용량이 너무 클 경우 푸시 이미지 삽입이 생략될 수 있습니다.

이미지 다운로드에 실패한 경우 serviceExtensionTimeWillExpire 함수로 나머지 제어권이 넘어가게 됩니다. 후처리가 필요한 경우 해당 함수에서 필요한 코드를 추가적으로 작성해주세요.