アプリの画面を開いているアプリケーションのプレビュー画面から隠す

iOS 7 以前でも、アプリ実行中にホームボタンを押してまた、そのアプリに戻ってきた場合には OS が作成した画面のスクリーンショットが表示されていました。プライバシーが気になるようなアプリでは Home に戻る直前の画面をそのまま次回前面になったときに使われると困ることがあります。家計簿等のお金を扱うアプリだと独自に暗証番号でロックできるようになっていますが、私が使っている「マネー2」は前面に戻ってきた時にちらっと前の画面がうつってからロック画面に移行してて何だかなぁと思っていました。iOS 7 になってからはホームボタンを2回押して出てくる最近使ったアプリ一覧画面にプレビュー画面が付いて Exposé 風になりました。Exposé 風になったことでますますこの問題が出てきてしまって気になったのでどうすれば良いのか試してみました。

iOS App Programming Guide

iOS App Programming Guide には

Remove sensitive information from views before moving to the background. When an app transitions to the background, the system takes a snapshot of the app’s main window, which it then presents briefly when transitioning your app back to the foreground. Before returning from your applicationDidEnterBackground: method, you should hide or obscure passwords and other sensitive personal information that might be captured as part of the snapshot.

と書かれています。アプリ側でよしなに対処するべきなんですね。

普通に実装しただけの場合

普通に前面にアプリがいるとき

f:id:griffin-stewie:20131011010803p:plain

そのまま Home ボタン2回押し

f:id:griffin-stewie:20131011010752p:plain

当然ながら凜々しい猫が丸見えです。

対策を実装

今回試したのは割と手抜きです。試した内容は以下のような感じです。

  • AppDelegate に Window と同じサイズの UIView を用意し、適当な色をつけておく
  • 各種ライフサイクルのデリゲートメソッドでその View を 表示 / 非表示 させる

View の実装

- (UIView *)privacyView
{
    if (_privacyView == nil) {
        _privacyView = [[UIView alloc] initWithFrame:self.window.frame];
        _privacyView.backgroundColor = [[UIColor orangeColor] colorWithAlphaComponent:0.95];
    }
    return _privacyView;
}

View の出し入れ

以下のようなメソッドを用意しておいて

- (void)showPrivacyView
{
    [self.window addSubview:self.privacyView];
}

- (void)removePrivacyView
{
    [self.privacyView removeFromSuperview];
}

Application のライフサイクルで出し入れを実装します。

- (void)applicationWillResignActive:(UIApplication *)application
{
    [self showBluredPrivacyWindow];
}

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    [self removeBluredPrivacyWindow];
}

applicationWillResignActive: はアプリが前面にあるときに Home ボタン2回押しの時も、Home に戻った時にも呼ばれます。applicationDidBecomeActive: はアプリが前面に戻ってきたときに呼ばれます。こちらもアプリが前面にあるときに Home ボタン2回押しした時も、Home に戻った時にも呼ばれます。

これらを実装すると以下のような感じになります。このスクリーンショットはアプリが前面にあるときに Home ボタン2回押しした時のものです。

f:id:griffin-stewie:20131011010741p:plain

折角なので iOS 7 っぽい磨りガラス風に

ということで iOS 7 風にしてみます。UIImage に対してブラーを掛けた画像を生成する部分は Apple のサンプルコードにある UIImage+ImageEffects を使ってみます。ただ、実際にはアプリの画面自体(View)を磨りガラス風画像にする必要があります。iOS 7 から - (UIView *)snapshotViewAfterScreenUpdates:(BOOL)afterUpdates がありますが、これは戻り値が UIView なので用途にあっていません。- (BOOL)drawViewHierarchyInRect:(CGRect)rect afterScreenUpdates:(BOOL)afterUpdates なら同様の処理を context に draw してくれるのでこちらを使って今の画面のスクリーンショットを取るようにします。

任意の View のスクリーンショットを撮って磨りガラス風の画像を生成するコードはこんな感じになります。

- (UIImage *)blurredSnapshotWithBlurType:(BlurEffectsType)type
{
    /// Original Code: iOS 7 blurring techniques — Damir Tursunović http://damir.me/posts/ios7-blurring-techniques
    UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, self.window.screen.scale);
    
    // There he is! The new API method
    [self drawViewHierarchyInRect:self.frame afterScreenUpdates:NO];
    
    // Get the snapshot
    UIImage *snapshotImage = UIGraphicsGetImageFromCurrentImageContext();

    // Now apply the blur effect using Apple's UIImageEffect category
    UIImage *blurredSnapshotImage = nil;
    switch (type) {
        case BlurEffectsTypeLight:
            blurredSnapshotImage = [snapshotImage applyLightEffect];
            break;
        case BlurEffectsTypeExtraLight:
            blurredSnapshotImage = [snapshotImage applyExtraLightEffect];
            break;
        case BlurEffectsTypeDark:
            blurredSnapshotImage = [snapshotImage applyDarkEffect];
            break;
        default:
            break;
    }

    // Be nice and clean your mess up
    UIGraphicsEndImageContext();
    
    return blurredSnapshotImage;
}

ImageContext を作って、- (BOOL)drawViewHierarchyInRect:(CGRect)rect afterScreenUpdates:(BOOL)afterUpdates で画面を draw し、Context から画像化。その画像を磨りガラス風画像に再加工して書き出します。

実際にこれを使うのには先ほどのメソッドを少し変更します。

- (void)showPrivacyView
{
    self.privacyView.backgroundColor = [UIColor colorWithPatternImage:[self.window blurredSnapshotWithBlurType:BlurEffectsTypeLight]];
    [self.window addSubview:self.privacyView];
}

Window を磨りガラス風画像にして、UIColor にして view の backgroundColor に突っ込みます。綺麗に実装するのなら self.privacyView を UIImageView にするべきですね。この例では手抜き実装となっています。するとこんな感じになります。

f:id:griffin-stewie:20131011010920p:plain

シミュレータだと対策が無効の場合がある

ハマりどころとしては、シミュレータの場合、ホームに戻ってから最近使ったアプリ一覧画面をみても期待通りの動きをしません。実機で試せば問題ありませんでした。このせいで小一時間ハマりました。

まとめ

実際にやってみると簡単です。View を磨りガラス風の画像にするコードはカテゴリメソッド等で使い回しが効く処理なのでプライバシーに配慮するべき画面があるアプリでは実装してみてはいかがでしょうか?iOS 6 以下の場合は一部メソッドが使えないので少し違った対処が必要になるかと思いますが今回の方法をベースすればさほど難しくはないと思います。

サンプルコードをおいておきます。

griffin-stewie/PrivacyProtectSample