DRIP – Drecom Invention Project エンジニアの小川です。
自分がメインで担当している Pass! が先日とうとう本リリースとなりました。
ドリコム、マップではじまるライブコミュニケーションアプリ 「Pass!(パス)」を正式リリース
http://www.drecom.co.jp/pr/2016/10/20161012.php
招待制を無くして誰でも使えるようになったので、是非触ってみてください。
そしてなんと最近 iMessage App 対応を果たし、ステッカーアプリとしても楽しめるようになりました!
今回は iMessage App 対応していくうちに得た、チュートリアルやサンプルコードだけではわかりづらい知見を共有したいと思います。
ステッカーアプリにおける "ステッカーパック" と "iMessage App" の違い
ざっくりまとめると、次の違いがあります。
ステッカーパック
- 指定された画像を用意してアセットに入れてビルドすれば完成
- 決められた画像をステッカーとして使うだけなら爆速で作れる
- Extension では無く1つのアプリとして扱う
- そのため App Store 上でも別アプリとして表示される
- 当然価格設定も別なので、ステッカーアプリは有料にするという選択肢が取れる
iMessage App
- 本体アプリに Extension として追加される
- ユーザーが「iMessage で使いたい!」となれば本体アプリを DL する -> ランキングに好影響する可能性あり
- 1からコード書く必要あり
- 動的なステッカーが作れる
- 撮影した画像をステッカーにしたり、指定された画像と画像を組み合わせてステッカーにしたり…
Pass! では「自分のアイコンを当てはめた着ぐるみをステッカーとして使える!」という企画だったので、必然的に iMessage App 一択となりました。
App Groups で本体アプリと連携
本体アプリで設定した自分のアイコンを iMessage App 側で取得するために、今回は App Groups を使って共有エリアを作成し、そこに自分のアイコンを保存する仕組みを考えました。
こんな感じのコードを持つ、本体と Extension 両方をターゲットにしたクラスを作成。
var urlImageCurrentFace: NSURL? { return self.fileUrl("currentImageFace.img") } var dataImageCurrentFace: NSData? { set { guard let data = newValue, let url = self.urlImageCurrentFace else { return } data.writeToURL(url, atomically: false) } get { guard let url = self.urlImageCurrentFace else { return nil } return NSData(contentsOfURL: url) } } func fileUrl(addPath: String) -> NSURL? { if let rootPath = NSFileManager.defaultManager().containerURLForSecurityApplicationGroupIdentifier("YOUR_APP_GROUP_ID") { return rootPath.URLByAppendingPathComponent(addPath) } return nil }
ステッカー用データの作成
MSSticker
を生成して投げれば、ステッカーとして相手にメッセージを送信できます。
このオブジェクトを生成する際 contentsOfFileURL: NSURL
に画像データのパスを入れます。
アセットに入れた画像をステッカーにして使いたい場合は
if let path = NSBundle.mainBundle().pathForResource("suit_file_name", ofType: "png"), let url = NSURL(fileURLWithPath: path) { sticker = MSSticker(contentsOfFileURL: url, localizedDescription: "Pass! Suit Data") }
を指定すれば OK です。
アプリ上で作成したステッカーすなわち動的なステッカーの場合は、一度データをローカル領域に保存してパスを確定し、それを指定します。
UIImage
や NSData
を直接指定できれば楽なのですが、そういった仕組みは用意されていません。残念。
func tmpPath(addPath: String) -> String { return NSTemporaryDirectory() + addPath } func tmpUrl(addPath: String) -> NSURL? { return NSURL(fileURLWithPath: NSTemporaryDirectory()).URLByAppendingPathComponent(addPath) } func getDataStickerUrl(fileName: String, isAnimation: Bool) -> NSURL? { let addPath = "\(filename).\(isAnimation ? "gif" : "png")" if NSFileManager().fileExistsAtPath(self.tmpPath(addPath)) { return self.tmpUrl(addPath) } return nil } func createSticker(fileName: String, isAnimation: Bool) -> NSStiker? { if let url = self.getDataStickerUrl(fileName, isAnimation: isAnimation) { sticker = MSSticker(contentsOfFileURL: url, localizedDescription: "Pass! Suit Data") } }
MSSticker
は自身に指定されたパス上にあるファイルの拡張子を見てデータの種類を把握するので、ローカル領域にデータを保存する際は正しい拡張子を指定しましょう。
実は Pass! でアイコン撮影時に撮影ボタンを長押しっぱなしにすると、アニメーションアイコンが撮影できるのです…!
なのでアイコンのアニメーション有無により PNG か Animation GIF かを判断してデータ化しています。
できる限りステッカー用の View を使うべし
MSSticker
を MSConversation
に挿入すれば、ステッカーを送信できます。
// MSMessagesAppViewController 継承クラスにて self.activeConversation?.insertSticker(sticker, completionHandler: nil)
ですがこれだけだと、ステッカーを長押ししてメッセージに貼る、というまさにステッカーな使い方はできません。
ステッカーとしてのユーザー操作を受け持ってくれる MSStickerView
MSStickerView
に MSSticker
を指定して表示すれば、コードを書かなくても
- タップすれば新規メッセージとしてステッカーを送信
- 長押しドラッグ&ドロップすれば既存メッセージにステッカーを貼れる
を実現してくれます。便利!
let stickerView = MSStickerView(frame: CGRect(x: 0, y: 0, width: 50, height: 50)) stickerView.sticker = sticker parentView.addSubview(stickerView)
MSStickerView
を生成してリスト表示してくれる MSStickerBrowserViewController
ステッカーパックのようなリスト表示がしたい場合は MSStickerBrowserViewController
を継承したコントローラを使えば簡単に作れます。
ステッカーサイズの種類、表示するステッカーの数、それに対応する MSSticker
オブジェクトを準備すれば MSStickerView
を良い感じにリスト表示してくれます。
表示する前に MSSticker
を準備しておくことが必須
どちらのやり方にせよ MSSticker
オブジェクトを表示前にちゃんと準備しておく必要があります。
データ生成に時間がかかる場合は「生成」と「表示」の処理を分けるなど、ひと工夫しておく事でユーザビリティの向上を期待できます。
Pass! では最初 MSStickerView
が表示されるタイミングなるたびにデータを生成していたため、アニメーションアイコンだった場合はリストの動作が重くなっていました。
そこで ver 2.0.5 からは裏でステッカー用のデータを生成しつつ、完了したステッカーから順に表示するやり方に変更しました。
本体アプリでアイコン変更しない限り、一度生成したデータをずっと使いまわしています。
アニメーションステッカーが一部キャッシュされる件
アニメーション画像を MSStickerView
として表示する場合は、自分で startAnimating()
を呼び出せばアニメーションしてくれるようになります。
その中でテストしている時に気付いたことがあります。
まず最初に適当なアニメーションアイコン(以下:古アニメ)を本体アプリで登録し、ステッカーとして表示、送信テストをします。
次に本体アプリで新しいアニメーションアイコン(以下:新アニメ)を作成して、ステッカーとして表示、送信テストをしたところ、以下のような現象が発生しました。
MSStickerView
として表示される箇所では古アニメが表示される- タップして新規メッセージとして送信する確認ビューでは新アニメが表示される
- 長押しドラッグして指に追従するビューでは新アニメが表示される
何故か 1
だけ古アニメになってしまうのです。
以降何回かアニメーションアイコンを更新してみるのですが、ずっと古アニメのままでした。
結果として、どうやらアニメーションステッカーの場合「ファイル名」でキャッシュを保持しているらしく、新しいデータでも同じ「ファイル名」のステッカーを使おうとすると、上記現象となる様子。
(ファイルパスではなく、ファイル名なところに注意!)
Pass! ではステッカーデータのファイル名を{適当な文字列}{StickerID}.{png or gif}
としており、本体アプリのアイコンデータが更新される度にこの {適当な文字列}
を変更して対処しています。
まとめ
- "ステッカーパック" と "iMessage App" には違いがあるので、やりたい事に合わせて選ぼう
- 本体アプリとデータを共有するなら App Groups 対応もしよう
- 動的なステッカーは一度ローカル領域に保存しよう
- できる限りステッカー用に用意された View を使おう
- アニメーションステッカーを使う場合はキャッシュに注意しよう
この情報が皆様のお役に立てれば、とても嬉しいです。
これからも新しい機能を取り入れつつ、DRIP としてチャレンジを続けていきます!