# iOS 푸시 메시지 연동

{% hint style="info" %}
**타 푸시 솔루션과 함께 사용할 수 있습니다**

타 푸시 솔루션과 함께 사용하려면 타 푸시 솔루션의 Swizzling 옵션을 비활성화해야 합니다.

Swizzling 비활성화 후, 해당 솔루션의 가이드를 참고하여 푸시 알림 처리를 수동으로 설정해 주세요.
{% endhint %}

{% hint style="danger" %}
FCM을 통한 iOS 푸시 메시지를 지원하지않습니다

iOS 푸시 메시지 사용을 위해서 APNs를 연동해주세요.
{% endhint %}

{% stepper %}
{% step %}
**APNs 설정하기**

iOS 앱에서 푸시 메시지를 사용하기 위해서는 핵클 워크스페이스와 APNs 연동 설정이 필요합니다.

자세한 내용은 [Apple Push Notification Service 설정](/external-link/crm-channels/apple-push-notification-service-integration.md)을 참고하세요.
{% endstep %}

{% step %}
**앱에 PushNotification Capability 추가**

Xcode로 `/ios/{APP_NAME}.xcworkspace` 파일을 열어 프로젝트를 연 후 프로젝트 설정의 `Signing & Capabilities` 탭에서 `+ Capability`를 아래와 같이 클릭해주세요.

![](/files/PNiAsTVQze9StBt5bbb3)

`Push Notifications`과 `Background Modes`를 추가해주세요.

![](/files/BlYVYAqrmLVZwdxMTDzK)

![](/files/ABblKDj7endt9vHJrl39)

그리고 `Background Modes`의 `Remote notifications`를 활성화해 주세요.

![](/files/re3CqjDxqPeuuJYty7h3)
{% endstep %}

{% step %}
**AppDelegate 설정**

{% hint style="info" %}
AppDelegate 설정을 해야 푸시 토큰 수집, 푸시 메시지 표시, 푸시 클릭 처리를 할 수 있습니다.
{% endhint %}

푸시 메시지 연동을 위해서는 AppDelegate 수정이 필요합니다.

Xcode로 `/ios/{APP_NAME}.xcworkspace` 파일을 열어 프로젝트를 연 후 AppDelegate 파일을 엽니다.

{% hint style="info" %}
AppDelegate 파일은 React Native 버전별로 언어가 다릅니다.

* React Native 0.77 이상 `AppDelegate.swift`
* React Native 0.76 이하 `AppDelegate.h` & `AppDelegate.mm`
  {% endhint %}
  {% endstep %}

{% step %}
**푸시 토큰 수집**

{% hint style="warning" %}
푸시토큰은 자동으로 수집되지 않습니다.

반드시 아래 코드를 통해 푸시토큰을 수집해야 푸시 수신을 받을 수 있습니다.
{% endhint %}

`AppDelegate`에 아래과 같이 `setPushToken` 메소드를 추가합니다.

{% tabs %}
{% tab title="AppDelegate.swift" %}

```swift
import UIKit
import React
import React_RCTAppDelegate
import ReactAppDependencyProvider
import Hackle

@main
class AppDelegate: RCTAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil
  ) -> Bool {
    // 기존 구현된 코드 내에 추가
    ...

    // iOS 앱에서 푸시 권한 요청
    let notificationCenter = UNUserNotificationCenter.current()
    notificationCenter.requestAuthorization(options:[.badge, .alert, .sound]) { (granted, error) in
        // Enable or disable features based on authorization.
    }
    notificationCenter.delegate = self
    application.registerForRemoteNotifications()

    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }

  override func application(
    _ application: UIApplication,
    didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
  ) {
    // 핵클 서버로 APNs 푸시 토큰 전달
    Hackle.setPushToken(deviceToken)
  }
...
}
```

{% endtab %}

{% tab title="AppDelegate.h (Objective-C)" %}

```objectivec
#import <RCTAppDelegate.h>
#import <UIKit/UIKit.h>
#import <UserNotifications/UserNotifications.h>

@interface AppDelegate : RCTAppDelegate <UIApplicationDelegate, UNUserNotificationCenterDelegate>

@end
```

{% endtab %}

{% tab title="AppDelegate.m (Objective-C)" %}

```objectivec
#import "AppDelegate.h"
#import <Hackle/HackleNotification.h>

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  ...
  // 기존 구현된 코드 내에 추가
  UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
  [center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert + UNAuthorizationOptionSound) completionHandler:^(BOOL granted, NSError * _Nullable error) {}];
  center.delegate = self;
  [[UIApplication sharedApplication] registerForRemoteNotifications];
  ...
}

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
  [HackleNotification setPushToken:deviceToken];
}
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}
**푸시 메시지 표시**

{% hint style="info" %}
백그라운드에서는 코드 구현 없이 자동으로 푸시가 표시됩니다.
{% endhint %}

{% hint style="warning" %}
포그라운드에서 푸시 표시를 위해서 반드시 아래 코드를 구현해야 합니다.
{% endhint %}

포그라운드 푸시 메시지 표시를 위해 `userNotificationCenter` 메소드를 추가합니다.

{% tabs %}
{% tab title="AppDelegate.swift" %}

```swift
import Hackle

extension AppDelegate: UNUserNotificationCenterDelegate {
  // Foreground push message
  func userNotificationCenter(
    _ center: UNUserNotificationCenter,
    willPresent notification: UNNotification,
    withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions
  ) -> Void) {

    if Hackle.userNotificationCenter(
      center: center, willPresent: notification, withCompletionHandler: completionHandler
    ) {
      // Succefully processed notification
      // Automatically consumed completion handler
      return
    } else {
      // Received not hackle notification or error
      print("Do something")

      if #available(iOS 14.0, *) {
        completionHandler([.list, .banner])
      } else {
        completionHandler([.alert])
      }
    }
  }
}
```

{% endtab %}

{% tab title="AppDelegate.m (Objective-C)" %}

```objectivec
// Foreground push message
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
       willPresentNotification:(UNNotification *)notification
         withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler
{
  if ([HackleNotification userNotificationCenter:center
                         willPresentNotification:notification
                           withCompletionHandler:completionHandler]) {
    // Succefully processed notification
    // Automatically consumed completion handler
    return;
  } else {
    // Received not hackle notification or error
    NSLog(@"Do something");
    completionHandler(UNNotificationPresentationOptionList | UNNotificationPresentationOptionBanner);
  }
}
```

{% endtab %}
{% endtabs %}

핵클에서 송신한 푸시가 아닌 경우 false가 리턴됩니다.
{% endstep %}

{% step %}
**푸시 클릭 처리**

{% hint style="danger" %}
핵클에서 제공하는 푸시 클릭 처리 함수를 호출하지 않으면 푸시가 정상적으로 처리되지 않습니다.

또한, 푸시 클릭 이벤트가 수집되지 않고 푸시 클릭률 지표를 이용할 수 없습니다.
{% endhint %}

푸시 클릭 처리를 위해 `handleNotification` 메소드를 추가합니다.

{% tabs %}
{% tab title="AppDelegate.swift" %}

```swift
import Hackle

extension AppDelegate: UNUserNotificationCenterDelegate {
  // push click
  public func userNotificationCenter(
    _ center: UNUserNotificationCenter,
    didReceive response: UNNotificationResponse,
    withCompletionHandler completionHandler: @escaping () -> Void
  ) {

    if let _ = Hackle.handleNotification(response: response) {
      // process hackle notification
    } else {
      // not hackle notification or error
      print("do something")
    }

    // handleNotification 에서 completionHandler를 호출하지 않으니
    // 핵클 푸시 여부에 관계없이 반드시 completionHandler를 호출해야 합니다.
    completionHandler()
  }
}
```

{% endtab %}

{% tab title="AppDelegate.m (Objective-C)" %}

```objectivec
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
         withCompletionHandler:(void (^)(void))completionHandler
{
  if ([HackleNotification userNotificationCenter:center
                              didReceiveResponse:response
                           withCompletionHandler:completionHandler]) {
    return;
  } else {
    completionHandler();
  }
}

```

{% endtab %}
{% endtabs %}

푸시 클릭 함수는 아래 순서로 처리를 합니다.

1. 핵클에서 송신한 푸시인지 확인
2. 푸시 클릭 이벤트를 핵클 서버로 송신
3. (deep link push인 경우) deep link 처리

핵클에서 송신한 푸시가 아닌 경우 nil이 리턴됩니다.
{% endstep %}

{% step %}
**푸시 메시지 테스트**

**토큰 확인**

* [사용자 식별자 확인하기 가이드](/development-guide/react-native/react-native-user-explorer.md) 를 통해 iOS 기기에 설정된 토큰을 확인합니다.
* [사용자 조회 가이드](/user-view/user-profile.md) 를 통해 특정 사용자에 할당 된 iOS 푸시 토큰을 확인할 수 있습니다.

**테스트**

* [푸시 메시지 테스트 발송 가이드](/crm-marketing/push-message-guide/create-campaign.md#id-3-1) 를 참고하여 푸시 메시지를 iOS 기기에서 확인합니다.
  {% endstep %}

{% step %}
**푸시 메시지 수신**

iOS는 빌드 환경에 따라 푸시 메시지 수신 여부가 다릅니다.

{% hint style="info" %}
APNs Key의 Environment을 `Sandbox & Production`으로 설정한 경우에도 아래와 같이 핵클 환경 및 앱 빌드 환경별로 푸시 메시지를 수신받을 수 있는 범위가 다릅니다.
{% endhint %}

<table><thead><tr><th width="175.78125">핵클 환경</th><th width="175.40625">APNs Environment</th><th>빌드 환경</th></tr></thead><tbody><tr><td>개발 환경,<br>개발/운영 테스트 푸시</td><td>Sandbox</td><td>Xcode에서 직접 실행,<br>개발용 프로비지닝</td></tr><tr><td>운영 환경</td><td>Production</td><td>TestFlight, Ad Hoc,<br>App Store 배포</td></tr></tbody></table>
{% endstep %}
{% endstepper %}

## 딥링크 이동

핵클 푸시 메시지는 클릭 시 딥링크 이동을 지원합니다.

iOS 환경에서 React Native 딥링크를 사용하는 방법은 [React Native 딥링크 가이드](https://reactnative.dev/docs/linking?syntax=ios) 에서 확인 가능합니다.

{% hint style="warning" %}
원격 JS 디버거에 연결된 경우 React Native의 `Linking.getInitialURL()` 가 `null`이 리턴되어 딥링크로 연결 될 [링크를 확인 못하는 문제](https://reactnative.dev/docs/linking?syntax=ios\&language=typescript\&ios-language=swift#getinitialurl)가 있습니다.
{% endhint %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.hackle.io/development-guide/react-native/push-message/rn-ios-push-message.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
