読者です 読者をやめる 読者になる 読者になる

NSBlogger

意識高いブログ

iOSアプリ開発におけるイースターエッグのすすめ

はじめに

この記事はiOS Advent Calendar 2015 - Qiita 19日目の記事です。
昨日はhachinobuさんのMVVMっぽい構成のデモアプリを公開してみるでした。
本日はイースターエッグについて。
イースターエッグとは簡単にいうと隠し機能のことです。余裕のある開発者が仕込んだ粋なお遊びですね。
発見したときはついつい友達に教えたくなります。イースターエッグが仕込まれているアプリおよび実装例について紹介しますね。

イースターエッグの例

まず、イースターエッグが仕込まれているアプリをいくつか紹介しますね。

Google検索

Google」という名前のGoogle検索アプリです。ロゴ部分をタップすると…!
f:id:Kamekiti:20151219115219g:plain
ちなみにロゴが新しくなる前は、なぞるとロゴがバラバラになりました。
www.youtube.com

スーモ

スーモ」アプリには複数のイースターエッグが仕込まれています。
トップの「借りる」または「買う」を16回タップすると…!
f:id:Kamekiti:20151219115414g:plain
また、「設定」→「このアプリについて」の空白部分でコナミコマンドを打つと…!
f:id:Kamekiti:20151219115514g:plain

Google Chrome

Google Chrome」のタブを開くと右上の四角の枠にタブ数が数字で表示されますが、タブを100個開くと… :)
f:id:Kamekiti:20151219115735g:plain
Google Chromeのネットワークエラー画面で恐竜をタップするとゲームができるのも有名ですね。
The Offline Dinosaur in Google Chrome is Actually a Game

Rumor

そろそろサービス終了する「Rumor」ですが、ロゴを長押しすると…!
f:id:Kamekiti:20151219115827g:plain
ちなみにRumorには他にもイースターエッグが仕込まれています。

Swarm

Swarm」でひっぱって更新をする際、ずーっと下にひっぱっていくと…!
f:id:Kamekiti:20151219120025g:plain

このように、色んなアプリにちょっとした遊び心が隠されています。特にGoogleのアプリにはイースターエッグが多く見られますね。

イースターエッグを仕込むメリット

ちょっとしたアプリの宣伝になります。発見したイースターエッグを教えたいという気持ちからクチコミに繋げられますね。また、開発者としての余裕を見せつけることができます。ぜひ余力があればイースターエッグを仕込んでみましょう。こういう遊び心は忘れたくないものです。

イースターエッグを仕込む

イースターエッグを仕込むには様々な方法がありますが、「ある特定の条件を満たしたときにアクションを起こす」というのが王道かと思います。iOSには下記のように、ユーザによるアクションがたくさんあります

  • 長押し
  • タップ
  • ピンチイン・アウト
  • パン
  • スワイプ
  • 回転
  • マルチタッチ
  • なぞる
  • 3D Touch
  • 振る
  • 傾ける

今回はこれらの中からイースターエッグとして使えそうなイベントをいくつか紹介しますね。ほぼコピペで使えるかと思いますのでぜひご参考ください。
(UIViewにイベントを付加した場合の例になります。)

長押し

ロゴやViewを長く押した場合に色が変わるなどそういった動作にどうぞ。minimumPressDurationが長押しと判定されるまでの時間になります。

let longPressGesture = UILongPressGestureRecognizer.init(target: self, action: "didLongPressGesture:")
longPressGesture.minimumPressDuration = 2.0
self.addGestureRecognizer(longPressGesture)
userInteractionEnabled = true
func didLongPressGesture(sender : UILongPressGestureRecognizer) {
    print("long pressed!")
}

マルチタッチ

multipleTouchEnabledをtrueにするだけ。5本指でタッチしたとき、2本指でスライドしたときなど、複数の指の動作を判定したい場合に使えます。

multipleTouchEnabled = true
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    print("\(event?.allTouches()?.count)")
}

振る

あまりiPhoneiPadを振ることはありませんが(文字取り消しのときくらい?)、逆に普段使わない動作をイースターエッグ発動のイベントとして使うのはありですね。

override func canBecomeFirstResponder() -> Bool {
    return true
}
override func motionBegan(motion: UIEventSubtype, withEvent event: UIEvent?) {
    if event?.type == .Motion && event?.subtype == .MotionShake {
        print("START:Shake!!")
    }
}
override func motionEnded(motion: UIEventSubtype, withEvent event: UIEvent?) {
    if event?.type == .Motion && event?.subtype == .MotionShake {
        print("END:Shake!!")
    }
}

3D Touch

iPhone 6s から登場した3D Touch。どれくらい強く押されたかを取得できます。アイコンを強く押したら動き出すなどいろいろな使い方ができそう。
注意点としてはiPhone 6s 以降の端末でないと利用できません。また、iPhone 6sでも3D Touchの機能をオフにしている場合があるので、下記のようにiOSのバージョンおよび3D Touchが有効かどうかをチェックする必要があります。
maximumPossibleForceを基準として今どれくらい強く押されているか判定できます。

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    switch UIDevice.currentDevice().systemVersion.compare("9.0", options: .NumericSearch) {
    case .OrderedDescending, .OrderedSame :
        if traitCollection.forceTouchCapability == .Available {
            print("3D Touch OK")
            for touch in touches {
                print("\(touch.force)")
                print("\(touch.maximumPossibleForce)")
            }
        } else {
            return
        }
    case .OrderedAscending : return
    }
}

コマンド入力

「スーモ」のイースターエッグで紹介したコナミコマンドなどに使えます。上下左右のスワイプを感知して、特定の動作をしたときにイースターエッグを発動させましょう。下記の例ではコナミコマンド(上下左右の動きのみ)を実装しています。

var gestures = [UISwipeGestureRecognizerDirection]()
var konamiCommands = [UISwipeGestureRecognizerDirection]()
// Add Gestures
let leftSwipe = UISwipeGestureRecognizer.init(target: self, action: "checkCommand:")
leftSwipe.direction = .Left
self.addGestureRecognizer(leftSwipe)
 
let rightSwipe = UISwipeGestureRecognizer.init(target: self, action: "checkCommand:")
rightSwipe.direction = .Right
self.addGestureRecognizer(rightSwipe)
 
let upSwipe = UISwipeGestureRecognizer.init(target: self, action: "checkCommand:")
upSwipe.direction = .Up
self.addGestureRecognizer(upSwipe)
 
let downSwipe = UISwipeGestureRecognizer.init(target: self, action: "checkCommand:")
downSwipe.direction = .Down
self.addGestureRecognizer(downSwipe)
 
// Konami Command
konamiCommands.append(upSwipe.direction)
konamiCommands.append(upSwipe.direction)
konamiCommands.append(downSwipe.direction)
konamiCommands.append(downSwipe.direction)
konamiCommands.append(leftSwipe.direction)
konamiCommands.append(rightSwipe.direction)
konamiCommands.append(leftSwipe.direction)
konamiCommands.append(rightSwipe.direction)
func checkCommand(gesture : UISwipeGestureRecognizer) {
    if gestures.count == konamiCommands.count {
        gestures = Array()
    }
    gestures.append(gesture.direction)
    var i = 0
    while i < gestures.count {
        if gestures[i] != konamiCommands[i] {
            gestures = Array()
            break
        }
        i += 1
    }
    if i == konamiCommands.count {
        print("input Konami Command!!")
    }
}

イースターエッグ実装時の注意点

イースターエッグを実装しているときはけっこうワクワクしますよね。
自分しか知らない機能を仕込んでいるときは悪い顔になっている気がします。
色々と楽しいイースターエッグですが、仕込む際に注意すべきことがあります。

1.本来の操作を遮ってはいけない

本来アプリが提供する機能を遮るようなイースターエッグは望ましくありません。
イースターエッグがあるせいで使い勝手が悪くなるのは本末転倒です。

2.アプリに負荷がかからない(クラッシュさせない)

イースターエッグを発動させたときの動作は十分パフォーマンスチェックをしましょう。
負荷がかかりすぎてアプリが重たくなったり、場合によってはクラッシュするようなことがあると台無しです。

3.バグを含めない

上記2点にも関連していますが、イースターエッグにバグを仕込むのはNG。
何度も綿密にテストを重ねる必要があります。
イースターエッグはあくまでユーザに対しての隠し機能なので、開発者同士でそのコードレビューは必要ですし、テスターの方にもしっかりテストしもらいましょう。

おわりに

イースターエッグを仕込めるくらい余裕をもって開発したいですね。