Viewのスナップショットをとるメソッド drawViewHierarchyInRect:afterScreenUpdates:がiOS 7から使えるようになりましたが、特定の環境で不具合が発生するみたいです。というメモ。

iPhone 6/6 PlusでUIViewのスナップショットAPIを利用する際に、
一瞬画面が拡大されてしまう不具合が発生します。

sample

サンプルコードは

iOS-Snapshot-Bug https://github.com/kshuin/iOS-Snapshot-Bug

に置いてあります。

発生条件

手元で確認している発生条件は以下の通りです。

  • iPhone 6/6 Plus対応していない(LaunchScreen。storyboardを利用していない)
  • iOS 7から利用できるdrawViewHierarchyInRect:afterScreenUpdates:を利用している
  • drawViewHierarchyInRect:afterScreenUpdates:の第2引数にYESを渡している。

iOS 7での発生条件は

  • UniversalアプリではないiPhone用アプリをiPadで実行している。(未確認)

です。

これらの条件から互換性のために画面をスケールアップ/ダウンしているアプリで発現するものと思われます。

回避策

Workaround 1

drawViewHierarchyInRect:afterScreenUpdates:の第2引数にNOを渡すことで、この問題を回避できます。
ただし、スナップショットを利用するタイミングによってはNOを渡すことで問題が起きる場合あります。

UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0.0f);

[self drawViewHierarchyInRect:self.bounds afterScreenUpdates:NO];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

Workaround 2

iOS 6 以前からあるAPIを利用することで回避できます。
しかしこの方法には2つの問題があります。

パフォーマンス

drawViewHierarchyInRect:afterScreenUpdates:は、従来のAPIに比べてパフォーマンス面でのメリットがあるとWWDC 2013の中で説明されています。従来のAPIを利用することはそのメリットを手放すことになってしまいます。サンプルのような一度だけ取得するようなものであれば気にならないかもしれませんが、移動するViewの背景に利用する場合などは問題がおきるかもしれません。

透過処理のサポート

サンプルを動かしてもらうとわかりますが、ナビゲーションバーの透過が無効になっています。
コードを工夫することで回避することもできるかもしれませんが、現時点では実現していません。

UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0.0f);

[self.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

まとめ

iOS 7の時代から修正されていないので問題としては根深いのかもしれません。というか修正が期待できません。
さっさとiPhone 6/6 Plus対応しろというAppleからのメッセージだと前向きに受け取りましょう。

他に回避方法があればぜひ教えてください。

(追記)

解決のヒントをくれた@qmiharaに感謝‼︎

参考情報 :