Push Notification
이 문서는 ChannelIO iOS SDK(이하 SDK)의 Push Notification 에 대해 서술한 문서입니다.
APNs 인증 정보 설정하기
Step 1. Push Notification 키 생성 (Key, Key ID)
이미 앱에서 푸시 알림을 제공하고 있다면, 아래 과정을 생략합니다.
- Certificates, Identifiers & Profiles > Keys 페이지에서, Apple Push Notifications service 가 활성화된 키를 발급합니다.
- Key ID를 확인합니다. 발급된 인증 키는 한 번만 다운로드할 수 있으므로 안전한 위치에 저장합니다.
- Account > Membership 페이지에서 Team ID를 확인합니다.
Step 2. 채널톡에 APNs Auth Key(.p8) 등록하기
- 채널톡(PC)를 실행하고, 설정 > 보안 및 개발 > 모바일 SDK 푸시 섹션으로 이동합니다.
- 위에서 다운로드한 Key file을 업로드하고, Key ID, Bundle ID, Team ID를 입력합니다.
Step 3. 채널톡에 Device token 등록하기
initPushToken
을 이용하여 채널톡에 푸시를 받을 기기의 device token을 등록합니다.
아래 예시를 참고하여, AppDelegate.swift
의 application(_:didRegisterForRemoteNotificationsWithDeviceToken:)
메서드에 ChannelIO.initPushToken(deviceToken: deviceToken)
를 추가합니다.
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
...
ChannelIO.initPushToken(deviceToken: deviceToken)
...
}
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
[ChannelIO initPushTokenWithDeviceToken:deviceToken];
}
채널톡 알림 이용하기
채널톡 알림 저장하기
백그라운드 상태에서 채널톡 푸시가 도착한 경우, 알림 내용을 저장하여 이후 앱이 포그라운드 상태가 될 때 저장된 내용을 보여줄 수 있습니다.
아래는, 푸시 알림을 탭했을 때, 채널톡 알림인 경우 푸시를 받았음을 알리고, 알림 내용을 저장하는 예시입니다.
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
) {
let userInfo = response.notification.request.content.userInfo
if ChannelIO.isChannelPushNotification(userInfo) {
ChannelIO.receivePushNotification(userInfo)
ChannelIO.storePushNotification(userInfo)
}
completionHandler()
}
//iOS 10 and above
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
withCompletionHandler:(void (^)())completionHandler {
NSDictionary *userInfo = response.notification.request.content.userInfo;
if ([ChannelIO isChannelPushNotification:userInfo]) {
[ChannelIO receivePushNotification:userInfo completion: nil];
[ChannelIO storePushNotification: userInfo];
}
completionHandler();
}
채널톡 채팅 열기
storePushNotification
을 통해 채널톡 푸시 알림 저장에 성공한 경우, openStoredPushNotification
함수를 통해 저장된 푸시 메시지를 담은 채팅을 열 수 있습니다.
이어지는 예시를 채널톡 채팅을 열기를 원하는 ViewController
에 추가해 주세요.
class ViewController : UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if ChannelIO.hasStoredPushNotification() {
ChannelIO.openStoredPushNotification()
}
}
}
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
if ([ChannelIO hasStoredPushNotification]) {
[ChannelIO openStoredPushNotification]
}
}
@end
팔로업 기능과 결합하기
푸시 알림과 SMS가 모두 도착하는 경우를 방지하기 위해, 채널에 푸시가 정상적으로 도착함을 알려야 합니다.
Signing & Capabilities
탭으로 이동하여, +Capability
버튼을 눌러 Background Mode
를 추가합니다.
Background fetch
와 Remote notification
을 체크합니다. 아래 사진을 참고합니다.
이후, 아래의 예시와 같이 application:didReceiveRemoteNotification
delegate 메서드에 아래와 같이 receivePushNotification
, storePushNotification
를 추가합니다.
func application(
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable : Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
if ChannelIO.isChannelPushNotification(userInfo) {
// This line
ChannelIO.receivePushNotification(userInfo)
ChannelIO.storePushNotification(userInfo)
}
completionHandler()
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
if ([ChannelIO isChannelPushNotification:userInfo]) {
[ChannelIO receivePushNotification:userInfo completion: ^{
completionHandler(UIBackgroundFetchResultNoData);
}];
[ChannelIO storePushNotification: userInfo];
} else {
completionHandler(UIBackgroundFetchResultNoData);
}
}
백그라운드 또는 앱 종료 상태에서도 정상적으로 receivePushNotification
을 보낼 수 있도록 Notification Extension을 참고하여 Extension을 추가합니다.
Notification Extension
이 단락은 SDK의 Notification Extension에 대하여 서술합니다. 이 Extension은 앱의 background 또는 terminated 상태에서도 채널톡 푸시에 대한 응답을 정상적으로 보낼 수 있도록 합니다.
채널톡은 이 Extension을 추가하기를 권장합니다. 만약 이를 추가하지 않은 경우에는, 팔로업 문자와 푸시 알림이 모두 도착할 수 있습니다.
Step 1. Extension Setup
- Xcode의 메뉴에서, File > New > Target… 탭을 선택합니다.
- 새로운 타겟을 고르는 창에서, Notification Service Extension을 선택합니다.
새로운 Target이 생성되고, Swift의 경우 두 개의 파일이 새로 생성됩니다. Objective-C의 경우 세 개의 파일이 새로 생성됩니다.
새로 생성된 파일에 대한 설명은 다음과 같습니다.
NotificationService.swift
: extension에서 사용하는 코드를 작성합니다.Info.plist
: extension Target의 세부 설정을 포함합니다.
Step 2. 패키지 설정
1. CocoaPods
새로 만든 Extension 타겟에 SDK 의존성을 추가합니다. Podfile
을 다음과 같이 수정합니다.
target 'your-notification-extention-target-name' do
pod 'ChannelIOSDK' ~
end
2. Carthage, Swift Package Manager
- 새로 만든 타겟의 프로젝트 설정으로 이동합니다.
General
탭으로 이동합니다.Frameworks and Libraries
항목에ChannelIOFront
패키지를 추가합니다.
Step 3. NotificationService 추가
아래 예시를 프로젝트 언어 설정에 맞게 추가합니다.
import UserNotifications
import ChannelIOFront
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var content: UNMutableNotificationContent?
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
self.content = (request.content.mutableCopy() as? UNMutableNotificationContent)
// this line need to combine sms feature during app terminated
ChannelIO.receivePushNotification(request.content.userInfo)
if let bca = self.content {
func save(_ identifier: String, data: Data, options: [AnyHashable: Any]?) -> UNNotificationAttachment? {
let directory = URL(fileURLWithPath: NSTemporaryDirectory())
.appendingPathComponent(ProcessInfo.processInfo.globallyUniqueString, isDirectory: true)
do {
try FileManager.default.createDirectory(
at: directory, withIntermediateDirectories: true, attributes: nil
)
let fileURL = directory.appendingPathComponent(identifier)
try data.write(to: fileURL, options: [])
return try UNNotificationAttachment.init(
identifier: identifier, url: fileURL, options: options
)
} catch {
}
return nil
}
func exitGracefully(_ reason: String = "") {
let bca = request.content.mutableCopy() as? UNMutableNotificationContent
contentHandler(bca!)
}
guard let content = (request.content.mutableCopy() as? UNMutableNotificationContent) else {
return exitGracefully()
}
let userInfo : [AnyHashable: Any] = request.content.userInfo
guard let attachmentURL = (userInfo["thumbUrl"] ?? userInfo["avatarUrl"]) as? String else {
return exitGracefully()
}
guard let imageData = try? Data(contentsOf: URL(string: attachmentURL)!) else {
return exitGracefully()
}
guard let attachment = save("\(attachmentURL.hashValue).png", data: imageData, options: nil) else {
return exitGracefully()
}
content.attachments = [attachment]
contentHandler(content.copy() as! UNNotificationContent)
}
}
override func serviceExtensionTimeWillExpire() {
if let contentHandler = contentHandler, let bac = self.content {
contentHandler(bac)
}
}
}
#import "NotificationService.h"
#import <ChannelIOFront/ChannelIOFront-swift.h>
@interface NotificationService ()
@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
@end
@implementation NotificationService
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];
[ChannelIO receivePushNotification:request.content.userInfo completion:nil];
if (self.bestAttemptContent != nil) {
UNMutableNotificationContent *content = request.content.mutableCopy;
NSDictionary *userInfo = request.content.userInfo;
NSString *attachmentURL = userInfo[@"avatarUrl"];
if (attachmentURL == nil) {
return [self exitGracefully:request withContentHandler:contentHandler];
}
NSURL *url = [[NSURL alloc] initWithString:attachmentURL];
NSData *imageData = [[NSData alloc] initWithContentsOfURL:url];
if (imageData == nil) {
return [self exitGracefully:request withContentHandler:contentHandler];
}
UNNotificationAttachment *attachment = [self save:[NSString stringWithFormat:@"%ld.png", attachmentURL.hash] data:imageData options:nil];
if (attachmentURL == nil) {
return [self exitGracefully:request withContentHandler:contentHandler];
}
content.attachments = @[attachment];
self.contentHandler(content.copy);
}
}
- (void)exitGracefully:(UNNotificationRequest* )request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
UNMutableNotificationContent *bca = request.content.mutableCopy;
contentHandler(bca);
}
- (UNNotificationAttachment *)save:(NSString *)identifier data:(NSData *)data options:(NSDictionary *)options {
NSURL *directory = [[[NSURL alloc] initFileURLWithPath:NSTemporaryDirectory() isDirectory:YES]
URLByAppendingPathComponent:NSProcessInfo.processInfo.globallyUniqueString];
NSError *error = [[NSError alloc] init];
[[NSFileManager defaultManager] createDirectoryAtURL:directory withIntermediateDirectories:YES attributes:nil error:&error];
if (error != nil) {
return nil;
}
NSURL *fileURL = [directory URLByAppendingPathComponent:identifier];
[data writeToURL:fileURL atomically:YES];
UNNotificationAttachment *ret = [UNNotificationAttachment attachmentWithIdentifier:identifier URL:fileURL options:options error:&error];
if (error != nil) {
return nil;
}
return ret;
}
- (void)serviceExtensionTimeWillExpire {
self.contentHandler(self.bestAttemptContent);
}
@end