VOYAGE GROUP エンジニアブログ

voyagegroup_techのブログ
VOYAGE GROUPエンジニアブログです。

iOS

iOS 8 で導入される選択肢付きの Push 通知を試してみた

こんにちは。VOYAGE GROUP で主に iOS アプリの開発をしている Swift はまだ様子見の @qmihara です。

iOS 8 では Push 通知に選択肢を付けられるようになりますね。
実際にどのようにすれば良いのか気になったので試してみました。

tech_blog-push_001
 
尚、本記事に記載されている内容は一般公開されているものをもとに記述しております。
画像につきましても一般公開されているものを引用しております。

環境

  • Xcode 6 beta 6
  • iOS 8 beta 5
  • Parse
お手軽に Push 通知を行うため Parse を使用しました。
便利です。

Push 通知の設定

(Push 通知の証明書の発行は従来と変わりありませんのでここでは省略します。)

iOS 8 から Push 通知の利用許可を求める方法が変わりました。
これまで `- registerForRemoteNotificationTypes:` でデバイストークンの取得、利用許可を求めるアラートの表示が行われていましたが、iOS 8 からはそれぞれ異なるメソッドを呼び出す必要があります。
[[UIApplication sharedApplication] registerForRemoteNotifications]; // <- Push 通知の利用登録(デバイストークンの発行)

UIUserNotificationSettings *notificationSettings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert|UIUserNotificationTypeBadge|UIUserNotificationTypeSound categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings]; // <- 利用許可を求めるアラートの表示
`- registerForRemoteNotifications` の結果は、従来と同様成功時は `- application:didRegisterForRemoteNotificationsWithDeviceToken:` が、失敗時は `- application:didFailToRegisterForRemoteNotificationsWithError:` が呼ばれます。

`- registerUserNotificationSettings:` の結果は新たに `- application:didRegisterUserNotificationSettings:` メソッドが呼ばれます。
引数の `notificationSettings` に実際にユーザーが許可した内容が反映されています(Alert のみ、等)。

選択肢の設定

選択肢の設定は Push 通知の利用許可を求める際に併せて行います。
選択肢はアクションという単位で作成することになり、複数のアクションは一つのカテゴリに属することになります。
カテゴリは複数定義することが可能で、Push 通知を送る際に定義済みのカテゴリを指定することになります。

以下はカテゴリ、アクションの作成例となります。
UIMutableUserNotificationAction *acceptAction = [UIMutableUserNotificationAction new];
acceptAction.identifier = @"ACCEPT_IDENTIFIER";
acceptAction.title = @"Accept";
acceptAction.activationMode = UIUserNotificationActivationModeBackground;
acceptAction.destructive = NO;
acceptAction.authenticationRequired = NO;

UIMutableUserNotificationAction *maybeAction = [UIMutableUserNotificationAction new];
maybeAction.identifier = @"MAYBE_IDENTIFIER";
maybeAction.title = @"Maybe";
maybeAction.activationMode = UIUserNotificationActivationModeBackground;
maybeAction.destructive = NO;
maybeAction.authenticationRequired = NO;

UIMutableUserNotificationAction *declineAction = [UIMutableUserNotificationAction new];
declineAction.identifier = @"DECLINE_IDENTIFIER";
declineAction.title = @"Decline";
declineAction.activationMode = UIUserNotificationActivationModeBackground;
declineAction.destructive = YES;
declineAction.authenticationRequired = NO;

UIMutableUserNotificationCategory *inviteCategory = [UIMutableUserNotificationCategory new];
inviteCategory.identifier = @"INVITE_CATEGORY";
[inviteCategory setActions:@[acceptAction, maybeAction, declineAction] forContext:UIUserNotificationActionContextDefault];
[inviteCategory setActions:@[acceptAction, declineAction] forContext:UIUserNotificationActionContextMinimal];

NSSet *categories = [NSSet setWithObjects:inviteCategory, nil];
UIUserNotificationSettings *notificationSettings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert|UIUserNotificationTypeBadge|UIUserNotificationTypeSound categories:categories];
[[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings];
 カテゴリは `UIMutableUserNotificationCategory`、アクションは `UIMutableUserNotificationAction` クラスを使用します。
アクションは識別子やタイトルなど必要な情報を設定します。

  • identifier
    • アクションを表す識別子
  • title
    • バナーやアラートに表示されるボタンタイトル
  • activationMode
    • Background、または Foreground を指定
    • Background であればアプリが起動することなく処理され、Foreground であればアプリが起動されます
  • destructive
    • ボタンタイトルを強調するかどうか
    • 削除など取り返しのつかない処理などで YES にする
  • authenticationRequired
    • YES を指定すると選択時にパスコードをの入力を求める
    • (iOS 8 beta 5 の時点では YES/NO に関わらず動きの違いはありませんでした。)
カテゴリには定義したアクションを設定するのですが、コンテキスト別でアクションを出し分けることができます。
コンテキストには以下の 2 種類があります。
  • UIUserNotificationActionContextDefault
    • ダイアログ表示時
    • 表示可能なアクションは最大 4 個
  • UIUserNotificationActionContextMinimal
    • バナー表示時
    • 表示可能なアクションは最大 2 個

選択肢付き Push 通知を送る

選択肢付きの Push 通知はペイロードに定義済みのカテゴリを指定することで送信することができます。
以下はカテゴリを指定したときのペイロード例です。
{
  "aps": {
    "alert": "Invitation from Jane Appleseed",
    "category": "INVITE_CATEGORY"
  }
}
tech_blog-push_002

tech_blog-push_003

次にアプリ側での通知の受け取り方です。
通知の選択肢がタップされた場合、`- application:handleActionWithIdentifier:forRemoteNotification:completionHandler:` メソッドが呼び出されます。
メソッドには定義したアクションの識別子が渡されますので、識別子に応じた処理を行うことになると思います。
最後に `completionHandler` を呼び出すのをお忘れなく。
- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo completionHandler:(void (^)())completionHandler {
    if ([identifier isEqualToString:@"ACCEPT_IDENTIFIER"]) {
        // ...
    } else if ([identifier isEqualToString:@"MAYBE_IDENTIFIER"]) {
        // ...
    }


    if (completionHandler) {
        completionHandler();
    }

}
選択肢ではなくバナー自体やダイアログの開くをタップした場合は、iOS 7 から追加された `- application:didReceiveRemoteNotification:fetchCompletionHandler:` が呼び出されます。
これまでの `- application:didReceiveRemoteNotification:` は呼ばれなくなっているのでご注意ください。

おまけ

選択肢付きの通知は Local Notification でも利用できます。

送り方は `UILocalNotification` に `category` プロパティが追加されていますので、ここにカテゴリの識別子をセットします。
UILocalNotification *localNotification = [UILocalNotification new];
localNotification.alertBody = @"Local notification";
localNotification.category = @"INVITE_CATEGORY";
[application scheduleLocalNotification:localNotification];
受け取りは `- application:handleActionWithIdentifier:forLocalNotification:completionHandler:` メソッドが呼ばれます。
- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)notification completionHandler:(void (^)())completionHandler {

    if ([identifier isEqualToString:@"ACCEPT_IDENTIFIER"]) {
        // ...
    } else if ([identifier isEqualToString:@"MAYBE_IDENTIFIER"]) {
        // ...
    }
    if (completionHandler) {
        completionHandler();
    }
}

まとめ

Push 通知に選択肢を付けられるようになったことで、通知からアプリを開く以外に任意の動作を行わせることができるようになりました。
例えば簡単なアンケートであれば Push 通知から答えてもらうなどもできそうですね。(Yes or No)

しかしバナー表示の場合は選択肢があることに気付きにくいので(通知を引っ張る、スワイプ)、アプリ内のチュートリアルなどで選択肢付きの Push 通知が届くこと、選択肢はどのようにして表示させるのか、ユーザーがわかるように伝える必要がありそうですね。

今回使用したサンプルコードは AppDelegate だけですが gist で公開しております。
Xcode で Single View Application でも何でもプロジェクトを作成し、AppDelegatem.m にコピペしてもらえればと思います。
Parse を使用していますので、アプリケーション ID などは別途設定してください。

【初心者でも簡単】facebookのPopでiOSアニメーションやってみた

こんにちは、VOYAGE GROUPエンジニアのガイアです
最近はiOSアプリの開発を主にやってます。
今までのiOSアプリ開発でアニメーションはほぼ触ったことがなかったのですが、今回アニメーションを行う用件が出てきたので、今回やったことを紹介します。
 

今年の5月始め、facebookがアニメーションエンジン「Pop」を発表しました。
「Pop」はfacebookがリリースしたアプリ「Paper」で使われているアニメーションライブラリをオープンソース化したものです。海外では盛り上がっています。
GithubでPopのページを見ると注目度の高さが分かります。

iOSでアニメーションは初めてですが、せっかくなのでPopを使ってみました。


- Pop

github pop
https://github.com/facebook/pop

facebook engineer blog
https://code.facebook.com/posts/234067533455773/introducing-pop-the-animation-engine-behind-paper/

このページでPopにおける目標が3つあげられています。
この目標自体がこのPopを使う利点でもあります。

  1. 一般的なアニメーションを便利に利用する
  2. ポップは拡張可能なフレームワークにする
  3. 開発者に優しいかつ強力なプログラミング·モデルを構築する


- install

CocoaPodsでインストールできます。

pod ‘pop’, ‘~> 1.0’

必要なところでインポートします。

#import <pop/POP.h>


- Code

では実際のコードを見ていきます。

SpringAnimation

Boundsを変更するばねのようなアニメーション

Spring
POPSpringAnimation *anim = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerBounds];
- POPSpringAnimationのインスタンスを作成
- LayerBoundsをアニメーションさせる

DecayAnimation

XYの位置を変更するDecayアニメーション

decay
 POPDecayAnimation *animX = [POPDecayAnimation animationWithPropertyNamed:kPOPLayerPositionX];
- POPDecayAnimationのインスタンスを作成
- LayerPositionのXをアニメーションさせる

2つアニメーションを定義すると合成される。

BasicAnimation

basic
POPBasicAnimation *anim = [POPBasicAnimation animationWithPropertyNamed:kPOPViewAlpha];
- POPBasicAnimationのインスタンスを作成
- kPOPViewAlpha:Alphaの値をアニメーション

普通のアニメーションも同様に記述できるため覚えることが少なくて済みます

PropertyNamedを変更すればアニメーションする要素を変更できます。

割と簡単にアニメーションできてしまいます。
Popを使わずにアニメーションしようとすると実装方法は複数あります。
Objective-Cでアニメーションまとめ
 

PopではNSObjectにカテゴリで関数を追加しているため、CALayerやUIViewなどで同じアニメーションのコードを書けます。
これがコードが統一され、「開発者に優しいかつ強力なプログラミング·モデル」を提供できている要因の一つのようです。(ってfacebook engineer blog に書いてありました)


- 拡張可能

2番目のポップは拡張可能なフレームワークにするとはどういうことでしょう?
実はアニメーションのカスタムプロパティを作ることができるようです。
Playing with pop (iv)

試しにLabelの数値をカウントアップするプロパティを作ってみました。

customProperty

プロパティがなくても割と簡単に作ってしまえるのはいいですね。


- おまけ  複数アニメーションを直列で実行したい

Blockを使えばできますが、簡単にできるようにライブラリを提供してくれている人がいました。
このライブラリを使えばBlockが使えるだけでなく、Popの構文をさらに短縮してくれます。
POP+MCAnimate

install

pod 'POP+MCAnimate', '~> 1.0'
#define MCANIMATE_SHORTHAND
#import <POP+MCAnimate.h>

code

まずは構文の短縮です

同じ動作をするコードです

pop
pop+MCAnimate

さらにアニメーションをBlockで分けて記述することが可能です。
ばねのようなアニメーションの後X軸で回転するアニメーションです。

mcAnimate

割と直感的に書けるのでいいと思いました。
(おそらくPopそのままでも直列のアニメーションを書けるとはおもいますが…)



実際にPopを使ってみて、まだまだできないこともありますができる限りPopを使いたいです。
(コードが統一されるため)
そして、こんな僕でも簡単にアニメーションが作れました !
ありがとうPop !
 

記事のサンプルコード https://github.com/Gaia-Murata/PopDemo

iPhoneアプリでABテスト

こんにちは、genesixで働くiOSエンジニア、@TachibanaKaoruです。

最近Web業界ではA/Bテストがすっかり定着してきます。

iPhoneアプリでもA/Bテストをしてみたいと思う方も多いと思います。
でも、AとBの二種類のパターンでユーザーインターフェースをデザインして、起動時にランダムでどちらかのパターンになるように実装して、アプリをリリースして、ユーザーのレスポンスを計測したあとで、また決まったユーザーインターフェースでアプリをリリースして……なんてことを考えると、ちょっと億劫になってきますよね。

そこで、アプリをリリースし直さずにiPhoneアプリのユーザーインターフェースを動的に変更する方法を実装してみました。
ここのユーザーインターフェースはどうしよう、ちょっとユーザーの反応をみて決めたいんだけど……と迷ったときに試してみてください。

サンプルソースはこちらにおいています。

ユーザーインターフェースを変更する(サーバーを使わない場合)

まずは、特にサーバーを使わない、簡単な方法です。

アプリのなかに複数のStoryboardを持っておけば、任意の時に、Storyboardを全部入れ替えることができます。

起動時に切り替えてもいいですし、ユーザーが選択したタイミングで切り替えてもいいですよね。

下記のように rootViewControllerを差し替えると、アプリ全体のユーザーインターフェースをまるまる変更することができます。

では、Storyboardをさしかえることで、なにができるんでしょうか。
ボタンや表示テキストの色や配色や位置など、画面のデザインやレイアウトを変えることができるのはもちろんですが、segueによる画面同士の遷移を変更することも可能です。
 
例えばトップ画面をタブ形式にするのか、それともテーブルビュー形式にするのかなどで悩むことも多いと思いますが、2パターンのStoryboardを作って、ユーザーの反応を測定してから決定することもできますね。
 

ユーザーインターフェースを変更する(サーバーを使った場合)

さて、さきほどの方法を使うと、アプリのUIをまるまる変更することはできますが、アプリの中に含まれたStoryboardファイルの内容を変更したり、数を増やしたりするためにはアプリを再リリースしないといけません。
 
これではちょっと柔軟性に欠けてしまうので、このStoryboardをサーバー側からとってくる仕組みにして、アプリの再リリースが必要ないようにしてみましょう。
サーバー側においておけば、いつでもStoryboardの内容を変更できますし、必要に応じて数を増やしたりすることも可能です。

まずは、Storyboardをダウンロードできるように、StoryboardがはいったNSBundleを作ります。
そして、そのNSBundleをzipにしてサーバーの適当な場所に配置します。

アプリ側では、そのzipファイルをダウンロード・解凍して、そのなかからStoryboardをとりだします。
あとはさきほどと同様に、rootViewControllerを変更すれば、アプリ全体のユーザーインターフェースを変更することができます。 
参考:Downalodable Storyboard

データ構造の変更に対応する

Storyboardで変更できるのは、画面のレイアウトやデザイン要素だけではありません。
実はStoryboardで、データ構造の変更に対応することもできます。

Storyboardでは、生成したオブジェクトのプロパティにアクセスできる「User Defined Runtime Attributes」という仕組みがあります。
 
例えば、この図に表示されているStoryboardのインスタンスはDITTableViewControllerというクラスのオブジェクトになっています。
普通にStoryboardで生成するだけでは、このオブジェクトのプロパティは初期化されただけの状態ですが、右の「User Defined Runtime Attributes」の部分に入力したい値をいれておけば、このオブジェクトが生成されたときに、指定した値が入力されます。

 44


この図の設定の場合には、下記のソースコードを書いた場合と同じ処理がStoryboardだけで実行されるわけです。


処理の流れとしてはこんな感じになります。
  1. DITTableViewControllerの初期化時にStoryboardで、APIのEndPointとして、「http://itunes.apple.com/jp/rss/topfreeapplications/limit=20/json」の値が設定される
  2. DITTableViewControllerのviewWillAppearで指定されたAPIのEndPointからJsonを取得し、かつ「feed.entry」で指定されたKey Pathの値をArrayとして内部に保持する

このAPIのEndPointと、表示データのArrayとして読み込むKey Pathの値をどちらもStoryboardで設定できるようにしておくことで、APIを変更した場合や、データベース構造が変更になった場合にも、Storyboardを差し替えるだけで対応できるようになりました。

また、画面に表示する要素も同じ仕組みで変更することが可能です。
この図では、UILabelを継承したDITLabelを使って、上記でダウンロードされたJsonファイルの中のim:artist.label要素を画面に表示するようにしています。

 UILabel

この仕組みを使うと、データベースの変更などにも、 Storyboardを変更するだけで対応することができます。

サンルコードのなかに、ソースコードを変更せずにStoryboardだけで「無料アプリリスト」と「映画リスト」をiTunesから取得して表示する画面を作ったので確認してみてください。 

測定

どのようにユーザーインターフェースをきりかえる方法を説明しましたが、A/Bテストで重要なのは確実な測定と分析です。

ユーザーインターフェース変更の仕組みをいれるだけではなく、それぞれのインターフェースでユーザーがどのように反応したのかを測定し、定期的に分析するのが重要です。

ここでは触れませんが、Google Analyticsを使って、適切な計測ポイントをいれておきましょう。

まとめ

Storyboardを切り替えるとアプリ全体のユーザーインターフェース全体を差し替えることができ、A/Bテストを実施できます。
また、 「User Defined Runtime Attributes」を使うことによって、データベースの変更やAPIの変更にも対応できる仕組みを作ることも可能です。

iOSでA/Bテストができるサービスもいくつかあるので、実装がめんどうであれば、そちらを利用してもいいと思います。テストをカスタマイズしたい場合や、自サーバーでデータを収集したい場合などには、是非この方法も試してみてください。

記事検索
QRコード
QRコード