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 です。

アプリ上で作成したステッカーすなわち動的なステッカーの場合は、一度データをローカル領域に保存してパスを確定し、それを指定します。
UIImageNSData を直接指定できれば楽なのですが、そういった仕組みは用意されていません。残念。

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 を使うべし

MSStickerMSConversation に挿入すれば、ステッカーを送信できます。

// MSMessagesAppViewController 継承クラスにて
self.activeConversation?.insertSticker(sticker, completionHandler: nil)

ですがこれだけだと、ステッカーを長押ししてメッセージに貼る、というまさにステッカーな使い方はできません。

ステッカーとしてのユーザー操作を受け持ってくれる MSStickerView

MSStickerViewMSSticker を指定して表示すれば、コードを書かなくても

  • タップすれば新規メッセージとしてステッカーを送信
  • 長押しドラッグ&ドロップすれば既存メッセージにステッカーを貼れる

を実現してくれます。便利!

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() を呼び出せばアニメーションしてくれるようになります。

その中でテストしている時に気付いたことがあります。
まず最初に適当なアニメーションアイコン(以下:古アニメ)を本体アプリで登録し、ステッカーとして表示、送信テストをします。
次に本体アプリで新しいアニメーションアイコン(以下:新アニメ)を作成して、ステッカーとして表示、送信テストをしたところ、以下のような現象が発生しました。

  1. MSStickerView として表示される箇所では古アニメが表示される
  2. タップして新規メッセージとして送信する確認ビューでは新アニメが表示される
  3. 長押しドラッグして指に追従するビューでは新アニメが表示される

何故か 1 だけ古アニメになってしまうのです。
以降何回かアニメーションアイコンを更新してみるのですが、ずっと古アニメのままでした。

結果として、どうやらアニメーションステッカーの場合「ファイル名」でキャッシュを保持しているらしく、新しいデータでも同じ「ファイル名」のステッカーを使おうとすると、上記現象となる様子。
(ファイルパスではなく、ファイル名なところに注意!)

Pass! ではステッカーデータのファイル名を{適当な文字列}{StickerID}.{png or gif}としており、本体アプリのアイコンデータが更新される度にこの {適当な文字列} を変更して対処しています。

まとめ

  • "ステッカーパック" と "iMessage App" には違いがあるので、やりたい事に合わせて選ぼう
  • 本体アプリとデータを共有するなら App Groups 対応もしよう
  • 動的なステッカーは一度ローカル領域に保存しよう
  • できる限りステッカー用に用意された View を使おう
  • アニメーションステッカーを使う場合はキャッシュに注意しよう

この情報が皆様のお役に立てれば、とても嬉しいです。
これからも新しい機能を取り入れつつ、DRIP としてチャレンジを続けていきます!

About the Author

小川 光典

アプリケーションエンジニア

iPhone 3G が日本で発売開始した頃からスマホネイティブを開発し続け、2015年にドリコム入社。現在 Crypto 部所属のエンジニア。