Skip to content

7 実装

ここでは、FANSHIP SDK の標準的な組み込み方法をご説明します。
具体的なファイル名やクラス名が登場する箇所については、実際に組み込むAppの構造などに応じて、適宜読み替えてください。

説明の途中で登場するソースコードについては、SampleAppプロジェクト内のAppDelegate.h および .m にて詳細をご確認いただけます。

SDK が提供するクラス、メソッド、プロパティの詳細については、document フォルダ内の html をご参照ください。

7.1 初期設定

最初の手順として、 PopinfoConfiguration.m において、値の設定が必要です。

popinfoApplicationID に対して、アプリケーション ID 文字列 を代入してください。

アプリケーション ID 文字列は、 FANSHIP 配信管理画面 からご確認ください。

位置情報などを利用する場合は、他の定数も変更する必要があります。
詳細は「9. 実装に関する特記事項」にて説明します。

7.2 ライフサイクルイベント

このセクションでは AppDelegate クラスを中心に、SDKを利用するにあたって必要な「App のライフサイクルにかかる実装」について説明します。

7.2.1 AppDelegate における実装

以下のように UserNotifications フレームワークと PopinfoReceiver を import し、PopinfoReceiver クラスのインスタンスとプロトコルの宣言をしてください。

#import <UserNotifications/UserNotifications.h>
#import "PopinfoReceiver.h"

@interface AppDelegate : NSObject <UIApplicationDelegate, UNUserNotificationCenterDelegate, PopinfoReceiverDelegate> {
    PopinfoReceiver *popinfoReceiver;
}
import UserNotifications
import PopinfoReceiver

@UIApplicationMain
class AppDelegate: UIApplicationDelegate, UNUserNotificationCenterDelegate, PopinfoReceiverDelegate {
    var popinfoReceiver: PopinfoReceiver?

    // 以下省略
}

Swift コード上で import エラーが発生する場合は、本ドキュメントの 5.3 Swift プロジェクト向けセットアップ における設定をご確認ください。

App 起動時にコールされるアプリケーションデリゲートメソッド application:didFinishLaunchingWithOptions: 内で、PopinfoReceiver クラスのインスタンスを生成するため、以下のように記述を行ってください。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [UNUserNotificationCenter currentNotificationCenter].delegate = self;
    popinfoReceiver = [PopinfoReceiver sharedReceiver];
    popinfoReceiver.delegate = self;
    // ...省略...
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    UNUserNotificationCenter.current().delegate = self
    self.popinfoReceiver = PopinfoReceiver.shared
    self.popinfoReceiver.delegate = self
    // ...省略...
}

App がアクティブになった時にコールされるアプリケーションデリゲートメソッド applicationDidBecomeActive: 内で、以下の記述を行ってください。
updateMessages: メソッドの引数のブロック内の記述については、SampleApp プロジェクト AppDelegate.m をご参照ください。

- (void)applicationDidBecomeActive:(UIApplication *)application 
{
    [popinfoReceiver loadSettings];
    [popinfoReceiver updateMessages:^(BOOL isOk, NSString *errorCode, NSArray *newMessages) {
        // プッシュ通知受信後、Appアイコンをタップした際に、お知らせ詳細画面への遷移するような処理を記述します。
        // SampleApp プロジェクト `AppDelegate.m` を参考に記述してください。
    }];
}
func applicationDidBecomeActive(_ application: UIApplication) {
    self.popinfoReceiver.loadSettings()
    self.popinfoReceiver.updateMessages{ (isOK, errorCode, newMessages) in 
        // プッシュ通知受信後、Appアイコンをタップした際に、お知らせ詳細画面への遷移するような処理を記述します。
        // SampleSwiftApp プロジェクト `AppDelegate.swift` を参考に記述してください。
    }
}

許諾ダイアログが初回起動時に表示されます

上記のように実装した場合、 App 初回起動時に 位置情報利用許諾ダイアログプッシュ通知許諾ダイアログ が発生します。
これらのダイアログを起動時に表示したくない場合は loadSettings ではなく loadSettings(skipOptIn:) をご利用ください。
詳細については 7.7 各種許諾ダイアログの表示 をご参照ください。

APNs トークンが iOS から付与される際にコールされるアプリケーションデリゲートメソッドを以下のように記述し、その中に FANSHIP の処理を記述してください。

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)dataToken
{
    [popinfoReceiver registerToken:dataToken];
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    self.popinfoReceiver.registerToken(deviceToken)
}

プッシュ通知の受信処理を、以下のように実装します。
UNUserNotificationCenterDelegate メソッドを以下のように記述し、その中に FANSHIP の処理を記述してください。

- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler   
    UNNotificationTrigger *trigger = response.notification.request.trigger;
    if ([trigger isMemberOfClass:[UNPushNotificationTrigger class]]) {
        UNNotificationContent *content = response.notification.request.content;
        [popinfoReceiver receiveNotification:content.userInfo];
    }
    completionHandler();
}
@available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
    let trigger = response.notification.request.trigger
    if trigger is UNPushNotificationTrigger {
        let content = response.notification.request.content
        self.popinfoReceiver.receiveNotification(content.userInfo)
    }
    completionHandler();
}

プッシュ通知到達後、FANSHIP 側で処理が終わった際にコールされるデリゲートメソッドを以下のように記述してください。
なお、引数 payload からキー名 has_detail で整数値を取得できますが、この値は 受け取ったプッシュ通知の配信タイプ を表しています。

has_detail の値 受け取ったプッシュ通知の配信タイプ
0 Push通知のみ
1 Push通知&コンテンツ配信

コンテンツ配信やプッシュのみ配信を使い分ける場合は、 has_detail を用いた分岐処理を忘れずに実装してください。

- (void)popinfoReceiver:(PopinfoReceiver *)pr didReceivePopinfoMessage:(NSInteger)messageId popup:(NSString *)popup payload:(NSDictionary *)payload
{
    BOOL hasDetail = payload && [payload objectForKey:@"has_detail"] && [payload[@"has_detail"] integerValue] == 1;
    if (hasDetail) {
        // お知らせ詳細画面を表示させる記述など
    } else {
        // PUSH 通知のみ配信時の処理を記述する
    }
}
func popinfoReceiver(_ popinfoReceiver: PopinfoReceiver, didReceivePopinfoMessage messageId: Int, popup: String, payload: [AnyHashable : Any]) {
    let hasDetail = (payload["has_detail"] as? Int) == 1
    if hasDetail {
        // お知らせ詳細画面を表示させる記述など
    } else {
        // PUSH 通知のみ配信時の処理を記述する
    }
}

App 起動中のプッシュ通知受信について

通知センターから行われるプッシュ通知は、Appがバックグラウンドにある場合にのみ発生します。
そのため、 Appがフォアグラウンド時にプッシュ通知が届いた場合、iOS からバナーやダイアログは表示されません
いきなりお知らせ詳細画面を表示するのではなく、ダイアログを表示してユーザーに知らせたい場合は、ここでフォアグラウンド状態かどうかを確認した上でダイアログを表示させる実装を行う必要があります。

7.2.2 SceneDelegate における実装

App が SceneDelegate を利用しない場合は、このセクションの実装は必要ありません。

Xcode 11 以降で利用できる SceneDelegate を利用している App の場合、UIApplicationDelegate ではなく UIWindowSceneDelegate に通知されるデリゲートが存在しますので、前述の AppDelegate を前提とした実装は 部分的に動作しません
そのため、SceneDelegate クラスを使用した実装に変更する必要があります。

SceneDelegate クラスを用いた実装はいくつか考えられますが、もっともシンプルな方法は UIWindowSceneDelegate へのデリゲートを契機に AppDelegate クラスに実装した処理を呼び出す方法です。

下記に実装例を示します。

// ...

@implementation SceneDelegate

- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions {
    if (![scene isMemberOfClass:UIWindowScene.class]) return;
}

- (void)sceneDidBecomeActive:(UIScene *)scene {
    id appDelegate = [[UIApplication sharedApplication] delegate];
    if ([appDelegate isMemberOfClass:AppDelegate.class]) {
        [(AppDelegate *)appDelegate applicationDidBecomeActive:[UIApplication sharedApplication]];
    }
}


- (void)sceneDidEnterBackground:(UIScene *)scene {
    id appDelegate = [[UIApplication sharedApplication] delegate];
    if ([appDelegate isMemberOfClass:AppDelegate.class]) {
        [(AppDelegate *)appDelegate applicationDidEnterBackground:[UIApplication sharedApplication]];
    }
}

@end
@available(iOS 13.0, *)
class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let _ = (scene as? UIWindowScene) else { return }
    }

    func sceneDidBecomeActive(_ scene: UIScene) {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
        appDelegate.applicationDidBecomeActive(UIApplication.shared)
    }

    func sceneDidEnterBackground(_ scene: UIScene) {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
        appDelegate.applicationDidEnterBackground(UIApplication.shared)
    }
}

また、SceneDelegate を利用している場合は AppDelegate.window の設定が無視されるため、これを用いた実装は正常に動作しません。

たとえば、rootViewController を取得する処理 self.window?.rootViewController は、SceneDelegate を利用している場合に nil を返します。

そのため、 AppDelegate ではなく、SceneDelegate に存在する window を使用する必要があります。

AppDelegate 内部で window を取得する実装例を下記に示します。

- (UIViewController *)rootViewController {
    if (@available(iOS 13.0, *)) {
        for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) {
            if (scene.activationState == UISceneActivationStateForegroundActive) {
                UIWindowScene *currentScene = (UIWindowScene *)scene;
                return currentScene.windows.firstObject.rootViewController;
            }
        }
        return nil;
    } else {
        return [[[[UIApplication sharedApplication] delegate] window] rootViewController];
    }
}
func rootViewController() -> UIViewController? {
    if #available(iOS 13.0, *) {
        guard let sceneDelegate = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate,
              let rootVC = sceneDelegate.window?.rootViewController else { return nil }
        return rootVC
    } else {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate,
              let rootVC = appDelegate.window?.rootViewController else { return nil }
        return rootVC
    }
}

7.3 バックグラウンドタスク

本 SDK は下記の目的のために、エンドユーザーが App を起動していないときもバックグラウンドタスクを実行させる必要があります。

  • ユーザーが App を起動していないときもGPS配信またはBluetooth配信を実施する
  • ユーザーの App 利用状況を集計し、FANSHIP 管理画面から確認できるようにする

バックグラウンドタスクを実行するための方法として、下記の3つの方法があります

  1. Background Tasks API の Background App Refresh Tasks (以下 BGAppRefresh) を利用する
  2. Background Tasks API の Background Processing Tasks (以下 BGProcessing) を利用する
  3. 旧バックグラウンドフェッチを利用する

BGAppRefresh と BGProcessing について

BGAppRefresh と BGProcessing はどちらも iOS 13 から追加された Background Tasks API に属する機能ですが、発動タイミングやタスクを実行可能な制限時間等が両者間で異なります。
Appleのドキュメント Choosing Background Strategies for Your App をご覧の上、ご利用のプロジェクトにおいて適切なものをお選びください。

なお、Background Tasks API の仕様に関するお問い合わせは、お手数ですが Apple Developer サポート にて行っていただきますようお願い致します。

iOS 13 未満では Background Tasks API は利用できません

Background Tasks API は、 iOS 13 から導入された機能であるため、iOS 12 以下においては動作いたしません。
そのため、 App のサポートOSバージョンに iOS 12 以下が含まれる場合は、「iOS 12 以下の端末で実行された場合に限り、旧バックグラウンドフェッチ処理を実行する」といった実装をご検討ください。

以下、各方法の実装手順をご説明します。

7.3.1 Background App Refresh Tasks を利用する

TARGETS > Capabilities > Background Modes にて、下記のチェックボックスをオンにします。

  • Background fetch

5.2.6.2 Background Task 設定 にそって BGAppRefresh 用の識別子を決め、以下の場所に追記します。

  • Info.plist 内のキー Permitted background task scheduler identifiers に対する値
  • SdkSettings 内のキー FanshipBGTaskSchedulerPermittedIdentifiers に対する値

-[AppDelegate applicationDidEnterBackground:] 内部にて、-[PopinfoReceiver scheduleAppRefreshTask] をコールします。

- (void)applicationDidEnterBackground:(UIApplication *)application {
    if (@available(iOS 13.0, *)) {
        [popinfoReceiver enterBackgroundTask];
    }
}
func applicationDidEnterBackground(_ application: UIApplication) {
    if #available(iOS 13.0, *) {
        popinfoReceiver.enterBackgroundTask()
    }
}

7.3.2 Background Processing Tasks を利用する

TARGETS > Capabilities > Background Modes にて、下記のチェックボックスをオンにします。

  • Background fetch
  • Background processing

5.2.6.2 Background Task 設定 にそって BGAppRefresh 用の識別子を決め、以下の場所に追記します。

  • Info.plist 内のキー Permitted background task scheduler identifiers に対する値
  • SdkSettings 内のキー FanshipBGTaskSchedulerPermittedIdentifiers に対する値

フラグ FANSHIP_BG_PROCESSING_TASK_USE を有効にします。以下のいずれかを実施してください。

  • Build Settings > Apple Clang - Processing > Preprocessor MacrosFANSHIP_BG_PROCESSING_TASK_USE=1 をセットする。
  • ビルド時の引数に OTHER_CFLAGS="-DFANSHIP_BG_PROCESSING_TASK_USE=1" をセットする

-[AppDelegate applicationDidEnterBackground:] 内部にて、-[PopinfoReceiver enterBackgroundTask] をコールします。

- (void)applicationDidEnterBackground:(UIApplication *)application {
    if (@available(iOS 13.0, *)) {
        [popinfoReceiver enterBackgroundTask];
    }
}
func applicationDidEnterBackground(_ application: UIApplication) {
    if #available(iOS 13.0, *) {
        popinfoReceiver.enterBackgroundTask()
    }
}

Background Tasks API 間の同時利用

一般に一つの App 内で BGAppRefresh と BGProcessing は同時に利用できますが、本SDKのバックグラウンドタスクは動作を安定させるために BGAppRefresh または BGProcessing のいずれか一方を利用して実装する形式となっています。

7.3.3 旧バックグラウンドフェッチを利用する

バックグラウンドフェッチ発動時にコールされるアプリケーションデリゲートメソッドを以下のように記述してください。

これは SDK バージョン 5.2.2 以下における標準的な実装方法でした。2020年11月時点ではiOS14においても問題なく動作します。

-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
    [popinfoReceiver enterBackgroundFetch];
    completionHandler(UIBackgroundFetchResultNewData);
}
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    popinfoReceiver.enterBackgroundFetch()
    completionHandler(.newData)
}

iOS 13 以降の Background Fetch API について

iOS 13 以降、 BackgroundTasks の追加にともない、旧バックグラウンドフェッチは deprecated となりました
ただし、 Apple は上記に関して廃止までの詳細なロードマップを発表しておらず、また iOS13 以上の OS のみで利用できることから、数年ほど(少なくともXcodeのサポートデバイス一覧から iOS12 以下が消失するまで)は引き続き利用できるものと予想されます。

Background Tasks API と旧バックグラウンドフェッチとの同時起動

Background Tasks API と旧バックグラウンドフェッチは、一般には同時に作動させるような利用が想定されていないと考えられており、SDKの動作不安定を招く可能性がありますので、推奨されません。

7.4 お知らせ一覧画面

受信したメッセージの一覧を表示するには、PopinfoListViewController クラスを利用します。

  • このビューコントローラを表示すると、「受信したメッセージの一覧」を表示するテーブルビュー が描画されます。
  • ユーザーがこのテーブルビューのセル(行)をタップすると、お知らせ詳細画面 に遷移します。

なお、この画面は ナビゲーションコントローラ がある状態でご利用ください。

詳細な組み込み例は、SampleApp/SampleSwiftApp プロジェクト内の AppDelegate をご参照ください。

PopinfoListViewController *listVC = [[PopinfoListViewController alloc] initWithNibName:@"PopinfoListViewController" bundle:[NSBundle mainBundle]]; 
[navigationController pushViewController:listVC animated:NO];
let listVC = PopinfoListViewController(nibName:"PopinfoListViewController", bundle:Bundle.main)
navigationController.pushViewController(listVC, animated: false)

クラスは継承してお使いください

FANSHIP SDK が提供する標準 UI は、適宜改良されていく可能性があります。
App 側でお知らせ一覧画面の UI をカスタマイズする場合は、 PopinfoListViewController クラスをそのまま修正するのではなく、これを継承した独自クラス で行っていただくことをおすすめします。

Storyboard を利用する場合

Storyboard を用いて実装する場合 は、 Custom Class に PopinfoListViewController を指定することも可能ですが、上記と同じ理由から PopinfoListViewController クラスを継承した独自クラス を指定することをおすすめします。

7.5 お知らせ詳細画面

受信したメッセージの詳細を表示するには、PopinfoDetailViewController クラスを利用します。

  • このビューコントローラは、表示する前に メッセージ ID の指定が必要です。
  • 表示すると受信したメッセージの詳細が描画されます。

詳細な組み込み例は、SampleApp/SampleSwiftApp プロジェクト内の AppDelegate をご参照ください。

PopinfoDetailViewController *detailVC = [[PopinfoDetailViewController alloc] initWithNibName:@"PopinfoDetailViewController" bundle:[NSBundle mainBundle]]; 
detailVC.messageId = messageId;
[navigationController pushViewController:detailVC animated:NO];
let detailVC = PopinfoDetailViewController(nibName:"PopinfoDetailViewController", bundle:Bundle.main)
detailVC.messageId = messageId
navigationController.pushViewController(detailVC, animated: false)

クラスは継承してお使いください

FANSHIP SDK が提供する標準 UI は、適宜改良されていく可能性があります。
App 側でお 知らせ詳細画面の UI をカスタマイズする場合は、PopinfoDetailViewController クラスを継承した独自クラス で行っていただくことをおすすめします。

Storyboard を利用する場合

Storyboard を用いて実装する場合 は、 Custom Class に PopinfoDetailViewController を指定することも可能ですが、上記と同じ理由から PopinfoDetailViewController クラスを継承した独自クラス を指定することをおすすめします。

7.6 ユーザー属性登録

この実装は必須ではありません。

PopinfoSegmentSettingsViewController を使い、エンドユーザーの属性を設定することができます。

FANSHIP では、性別や年齢層、趣味などといった属性をエンドユーザーに設定することができます。
設定された属性は、「 属性絞り込み配信 」などで利用することができます。

ご利用には申請が必要です

エンドユーザーに提示する属性および選択肢は、利用開始時に申請 する必要があります。
SDK側から変更することはできません

エンドユーザーに属性を設定する方法は2通りあり、API を経由して設定する方法と、エンドユーザーが属性設定画面から視覚的に設定する方法があります。

7.6.1 API として設定する方法

PopinfoReceiversetSegments:forKey:completion: メソッドを利用します。詳細は「iPhone 用 SDK 組み込みガイド_追加機能」をご参照ください。

7.6.2 属性設定画面からユーザーが視覚的に設定する方法

属性設定画面 PopinfoSegmentSettingsViewController を表示し、ユーザーに視覚的に属性を選択してもらう方法です。
この画面の実装例は SampleApp/SampleSwiftApp プロジェクト内の AppDelegate に記述していますので、ご参照ください。

PopinfoSegmentSettingsViewController *segmentVC = [[PopinfoSegmentSettingsViewController alloc] initWithNibName:@"PopinfoSegmentSettingsViewController" bundle:[NSBundle mainBundle]];
segmentVC.delegate = self;
let segmentVC = PopinfoSegmentSettingsViewController(nibName:"PopinfoSegmentSettingsViewController", bundle:Bundle.main)
segmentVC.delegate = self;

7.7 各種許諾ダイアログの表示

この実装は必須ではありません。

iOSではプッシュ通知によるお知らせ配信を行う場合、および位置情報を利用したお知らせ配信を行う場合のそれぞれにおいて、必ず エンドユーザーに利用許諾を行う必要があります。

これまでは -[PopinfoReceiver loadSettings] をコールしたタイミングで許諾処理が行われていましたが、 SDK バージョン 6.1.0 からは任意のタイミングで自動的に許諾処理を発生させることができるようになりました。

7.7.1 初回起動時の許諾処理をスキップする

メソッド -[PopinfoReceiver loadSettingsWithOptIn:] に引数 YES を与えてコールすると、初回起動時の許諾処理をスキップすることができます。

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    [[PopinfoReceiver sharedReceiver] loadSettingsWithSkipOptIn:YES]
    // ...
}
func applicationDidBecomeActive(_ application: UIApplication) {
    PopinfoReceiver.shared.loadSettings(skipOptIn: true)
    // ...
}

初回起動時の許諾処理をスキップした場合、許諾処理を発生させるまでの間は プッシュ通知と位置情報関連機能が利用できません

7.7.2 プッシュ通知許諾を発生させる

プッシュ通知許諾を発生させるには、任意のタイミングで -[PopinfoReceiver requestPushNotificationAuthorizationWithCompletion:] をコールします。
エンドユーザーによる許諾結果やエラーは completion handler の中で取得することができます。

下記はプッシュ通知許諾の実装例です。

[[PopinfoReceiver sharedReceiver] requestPushNotificationAuthorizationWithCompletion:^(BOOL granted, NSError *error) {
    // 許諾処理後の任意の処理
}];
PopinfoReceiver.shared.requestPushNotificationAuthorization { granted, error in
    // 許諾処理後の任意の処理
}

7.7.3 位置情報利用許諾を発生させる

位置情報利用許諾を発生させるには、任意のタイミングで -[PopinfoReceiver requestLocationAuthorization] をコールします。
エンドユーザーによる許諾結果は、デリゲートメソッド popinfoReceiver:detectedNewLocationAuthorizationStatus: に通知されます。

下記は位置情報利用許諾の実装例です。

- (void)applicationDidBecomeActive:(UIApplication *)application {
    //...
    [[PopinfoReceiver sharedReceiver] requestLocationAuthorization];
    //...
}
func applicationDidBecomeActive(_ application: UIApplication) {
    //...
    PopinfoReceiver.shared.requestLocationAuthorization()
    //...
}

エンドユーザーによる許諾結果を取得したい場合は、 PopinfoReceiverDelegate プロトコルに準拠したクラスにおいて、以下のように実装します。

- (void)popinfoReceiver:(PopinfoReceiver *_Nonnull)popinfoReceiver detectedNewLocationAuthorizationStatus:(CLAuthorizationStatus)status {
    // 許諾処理後の任意の処理
}
func popinfoReceiver(_ popinfoReceiver: PopinfoReceiver, detectedNewLocationAuthorizationStatus status: CLAuthorizationStatus) {
    // 許諾処理後の任意の処理
}