2016年6月14日火曜日

UIViewのframeとboundsプロパティの違いについて

なんとなく違いは分かっていましたが、いつもframeで事足りていたので、正確には理解していなかったのと、boundsのoriginを変更した時の挙動が分からなかったので調べてみました。

frameとboundsの座標系の違い

簡単に言ってしまうと、frameとはsuperviewを基点に考えた座標系です。boundsとはローカルviewを起点に考えた座標系になります。

左側の図はviewの左上の原点が(0, 0)の幅150、高さ200のview。 右側の図はviewの左上の原点が(50, 50)で同サイズのview。 viewのframe値に関係なくboundsは自身の座標系を示すので常にoriginは(0, 0)を示していますね。sizeはframeもboundsも同じ値で(150, 200)です。 この例ではboundsのoriginはframeのoriginに影響を受けない事は分かりますが、そこまでしか分かりませんね。実はviewを拡大縮小・回転してみるともっと違いがはっきりとして来ます。以下で回転した場合の例を示します。

拡大縮小・回転時には注意が必要

viewを回転したり、拡大縮小した場合はどうなるのでしょうか。これでなんとなく違いが見えてくると思います。

下の図は左側にあるsubviewを右回転したものが右側の図になります。subviewには分かりやすい様に画像を貼り付けています。

frameの値はアファイン変換(回転、拡大縮小)により本来のframe値ではなく、変換後のviewを囲う様な値で再定義されてしまっています(青枠)。ですので、もう描画時の座標計算には使えないでしょう。この事は、「iOS view プログラミングガイド」にこう記されています。

重要: ビューのtransformプロパティが恒等変換でない場合、そのビューのframeプロパティの値 は未定義となり、無視する必要があります。ビューに変換を適用する場合は、ビューのboundsおよ びcenterプロパティを使用して、ビューのサイズと位置を取得する必要があります。サブビューの フレーム矩形はビューの境界に対して相対的であるため、有効なままです。

transformした場合はframe値を使わずに、boundsやcenterプロパティ値を使用して、viewサイズや中心点を求める必要があるようですね。回転してもboundsプロパティ値の方は変化無しです。ローカルviewを起点に考えているのでsuperviewがどのように変化しても値に変化が現れないのは理解出来ます。

以下に、viewを1回転した場合のアニメーションgifを載せています。viewの回転角度によってframeが刻々と変化する様子が分かると思います。

上記のアプリはGitHubで公開していますので、宜しかったらダウンロードして実際に動かしてみてください。レポジトリは ViewFrameBoundsIndicatorで公開しています。

bounds のoriginプロパティ

さて、boundsのoriginプロパティ値は、デフォルト値で常に(0,0)を指しています。この値を変更するとどうなるのでしょうか。

実はsubviewを作成しただけではこの違いに気付くことが出来ません。subviewの中に更にsubviewを作成して初めて違いが見えてきます。 図では、subviewの中にUIImageViewをsubviewとしてaddしています。因みに画像はお寿司ですよ。

それではboundsのoriginを変更してみましょう。確認用のアプリからboundsのoriginを変更してみます。

わかります?boundsのoriginを変更するとsubviewの中がスクロールしているかのように見えます。 もっと分かりやすくするためにsubviewのclipToBoundsをoffにしてみます。因みに緑枠がboundsの矩形で、青枠がfraneの矩形です。

これでイメージがついたのではないでしょうか。subviewのbounds originを変更する事で、そのsubviewのsubviewとの座標系をずらすことが出来ます。offset値を設定しているイメージでしょうか。以下に静止画のイメージも載せておきます。subviewのsubview座標をずらして表示していることがイメージ出来ますでしょうか。

ps: WWDC16 Keynote 見ながら書いてます。ヤバいそろそろ寝ないと!

GitHubにも上げていますが、ViewControllerのソースを貼り付けておきます。

  1. import UIKit
  2.  
  3. class ViewController: UIViewController {
  4.  
  5. @IBOutlet weak var boundXSlider: UISlider!
  6. @IBOutlet weak var boundYSlider: UISlider!
  7. @IBOutlet weak var rotationSlider: UISlider!
  8. @IBOutlet weak var detailTextView: UITextView!
  9.  
  10. var targetView: UIView!
  11. var targetSubView: UIImageView!
  12. var frameLayer: CALayer!
  13. var boundsLayer: CALayer!
  14. @IBOutlet weak var clipBoundsSwitch: UISwitch!
  15.  
  16. override func viewDidLoad() {
  17. super.viewDidLoad()
  18.  
  19. targetView = UIView(frame: CGRect(x: 0, y: 0, width: 150, height: 200))
  20. targetView.center = CGPoint(x: view.frame.width / 2, y: 200)
  21. targetView.backgroundColor = UIColor.whiteColor()
  22. targetView.layer.contentsScale = UIScreen.mainScreen().scale
  23. targetView.clipsToBounds = true
  24.  
  25. // sub sub view
  26. targetSubView = UIImageView(frame: CGRect(x: 0, y: 0, width: 150, height: 200))
  27. targetSubView.image = UIImage(named: "C789_unitoikuramaguro_TP_V.jpg")
  28. targetSubView.contentMode = UIViewContentMode.TopLeft
  29. targetSubView.layer.contentsScale = UIScreen.mainScreen().scale
  30.  
  31. // add to sub view
  32. targetView.addSubview(targetSubView)
  33. // add to superview
  34. // view.addSubview(targetView)
  35. view.insertSubview(targetView, atIndex: 0)
  36.  
  37. // layer of indicating frame border line
  38. frameLayer = CALayer()
  39. frameLayer.frame = targetView.frame
  40. frameLayer.backgroundColor = UIColor.clearColor().CGColor
  41. frameLayer.borderColor = UIColor.blueColor().CGColor
  42. frameLayer.borderWidth = 2.0
  43. view.layer.addSublayer(frameLayer)
  44.  
  45. // layer of indicating bounds border line
  46. boundsLayer = CALayer()
  47. boundsLayer.frame = targetView.bounds
  48. boundsLayer.backgroundColor = UIColor.clearColor().CGColor
  49. boundsLayer.borderColor = UIColor.greenColor().CGColor
  50. boundsLayer.borderWidth = 2.0
  51. targetView.layer.addSublayer(boundsLayer)
  52.  
  53. // information
  54. detailTextView.text = "frame: \(targetView.frame)\nbounds: \(targetView.bounds)\ncenter: \(targetView.center)"
  55. print("targetView frame : \(targetView.frame)")
  56. print("targetView bounds: \(targetView.bounds)")
  57. print("targetView center: \(targetView.center)")
  58. // slider handler settings
  59. boundXSlider.addTarget(self, action: #selector(changeBoundsHandler), forControlEvents: UIControlEvents.ValueChanged)
  60. boundYSlider.addTarget(self, action: #selector(changeBoundsHandler), forControlEvents: UIControlEvents.ValueChanged)
  61. rotationSlider.addTarget(self, action: #selector(changeBoundsHandler), forControlEvents: UIControlEvents.ValueChanged)
  62.  
  63. }
  64.  
  65. override func didReceiveMemoryWarning() {
  66. super.didReceiveMemoryWarning()
  67. // Dispose of any resources that can be recreated.
  68. }
  69.  
  70.  
  71. func changeBoundsHandler(sender: AnyObject) {
  72.  
  73. if boundXSlider == sender as! NSObject {
  74. // x
  75. targetView.bounds.origin.x = (targetSubView.image!.size.width - targetView.bounds.width) * CGFloat(boundXSlider.value) / 2
  76.  
  77. } else if boundYSlider == sender as! NSObject {
  78. // y
  79. targetView.bounds.origin.y = (targetSubView.image!.size.height - targetView.bounds.height) * CGFloat(boundYSlider.value) / 2
  80. } else if rotationSlider == sender as! NSObject {
  81. // rotation
  82. let transform = CGAffineTransformMakeRotation(2 * CGFloat(M_PI) * CGFloat(rotationSlider.value))
  83. targetView.transform = transform
  84. }
  85. // update layer of frame
  86. frameLayer.frame = targetView.frame
  87. boundsLayer.frame = targetView.bounds
  88.  
  89. // information
  90. detailTextView.text = "frame: \(targetView.frame)\nbounds: \(targetView.bounds)\ncenter: \(targetView.center)"
  91.  
  92. }
  93.  
  94. @IBAction func changeSwitchHandler(sender: AnyObject) {
  95. targetView.clipsToBounds = clipBoundsSwitch.on
  96.  
  97. }
  98.  
  99. }
  100.  
GitHub URL: https://github.com/takuran/ViewFrameBoundsIndicator
参考URL http://stackoverflow.com/questions/1210047/cocoa-whats-the-difference-between-the-frame-and-the-bounds/28917673#28917673

0 件のコメント :

コメントを投稿