Swiftで遊ぼう! on Hatena

あしたさぬきblogでやってた初心者オヤジのiOSプログラミング奮闘記がHatenaに来ました

Swiftで遊ぼう! - 656 - layerにcontext

次のTable Viewのチュートリアルを拡張しています。

yataiblue.hatenablog.com

標準で用意されたUIViewに表現力を加えたい場合は、独自にビットマップイメージをlayerとして加える必要があります。contextは背景のことで、UIViewに背景として飾り枠や陰を付けたりできるってことですね。ということでカスタムメソッド「snapshotOfCell()」の内容を詳しくみていきます。

func snapshotOfCell(inputView: UIView) -> UIView {
  UIGraphicsBeginImageContextWithOptions(inputView.bounds.size, 
                           false, 0.0)
  let context = UIGraphicsGetCurrentContext()!
  inputView.layer.renderInContext(context)
  let image = UIGraphicsGetImageFromCurrentImageContext() as UIImage
  UIGraphicsEndImageContext()
        
  let cellSnapshot : UIView = UIImageView(image: image)
  cellSnapshot.layer.masksToBounds = false
  cellSnapshot.layer.cornerRadius = 0.0
  cellSnapshot.layer.shadowOffset = CGSizeMake(-5.0, 0.0)
  cellSnapshot.layer.shadowRadius = 5.0
  cellSnapshot.layer.shadowOpacity = 0.4
  return cellSnapshot
 }

func UIGraphicsBeginImageContextWithOptions(_ size: CGSize, _ opaque: Bool, _ scale: CGFloat)

特定のオプションを与えてビットマップ形式のイメージを背景として作るメソッドで、オプションはパラメータになります。

まず最初のパラメータは「サイズ(size)」です。UIViewのプロパティである「bounds*1」のサイズ(size)を与えて背景を作ります。2つ目のパラメータは「不透明度(opaque)」で、YESなら完全不透明でアルファチャンネルを無視できます。NOならばアルファチャンネルを含んでいるため必ず設定をしてやらないといけないようです。最後のパラメータは「スケール(scale)」ですが、このスケールの意味が良くわかりません。「0.0」にすると、iOSデバイスのディスプレーサイズに合わせるようです。この値を大きくすれば、拡大されるってことでしょうか?

どちらにしろ、このメソッドの初っぱなが次のコードだけです。

UIGraphicsBeginImageContextWithOptions(inputView.bounds.size, 
                     false, 
                       0.0)

inputViewは、カスタムメソッド「snapshotOfCell()」で引数として与えられるUIView型のパラメータです。しかし、よく考えてみると、実際に引数としてUITableViewCell型の「cell」が渡されています。UITableViewCellはUIViewの派生クラスなので、こういう場合「アップキャスト」と呼びます*2。そしてアップキャストは常に成功することが保証されているので。UITableViewCell型の値をUIViewとして受け取ることは可能だってことになります。そして2つ目のパラメーターは「false」なんで、アルファチャンネルの設定が後で必要になります。そしてスケールは「0.0」なんでディスプレイの画面サイズに合わせてくれます。

このメソッドでcellの大きさに合わせた背景画像の空キャンバスが生まれたと言っていいのでしょう。まだMVCの「V」に出現した訳ではありません。まだメモリー空間という母体に宿った胎児のようなものです。

func UIGraphicsGetCurrentContext() -> CGContext?

まず背景という空キャンバスが生まれたのですがメモリ空間に存在するだけです。それを実体化(インスタンス)しないと生まれてきません。このメソッドを使って背景を実体化させます。

let context = UIGraphicsGetCurrentContext()!

func renderInContext(ctx: CGContext)

そして背景を使ってlayerに組み込むメソッドです。これで目的のUIViewのレイヤーに背景を組み込みました。このメソッドの一連の流れは構文のようなものです。1度理解して覚えてしまえば疑問にも思わなくなるでしょう。早くそうなりたいものです。

inputView.layer.renderInContext(context)

func UIGraphicsGetImageFromCurrentImageContext() -> UIImage!

それにしても「context」って扱いが面倒くさいですね。contextを組み込んだviewからUIImageとしてcontextのビットマップイメージを抜き出すメソッドです*3

let image = UIGraphicsGetImageFromCurrentImageContext() as UIImage

UIGraphicsEndImageContext()

これは後片付けのメソッドです。メモリー空間に存在する背景画像の空キャンバスを消去します。このステップを入れないと無駄なメモリー消費が残ってしまうということでしょう。

ここまでの流れは何となく理解できましたが、本当に面倒くさいステップですね。多分数日後に忘れてしまうでしょう(^_^;) 何度も繰り返して覚えるしかないようです。

今日はここまで。

*1:boundsって何だったかな? なんて人はいないと思うけど、これです->Swiftで遊ぼう! - 249 - UIViewの座標システム - Swiftで遊ぼう! on Hatena

*2:派生クラスにキャストするのはダウンキャストですね。

*3:自分で書いていて、なんか良くわからなくなりますね。