なんとなく違いは分かっていましたが、いつもframeで事足りていたので、正確には理解していなかったのと、boundsのoriginを変更した時の挙動が分からなかったので調べてみました。
簡単に言ってしまうと、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プロパティ値を使用して、viewサイズや中心点を求める必要があるようですね。回転してもboundsプロパティ値の方は変化無しです。ローカルviewを起点に考えているのでsuperviewがどのように変化しても値に変化が現れないのは理解出来ます。
以下に、viewを1回転した場合のアニメーションgifを載せています。viewの回転角度によってframeが刻々と変化する様子が分かると思います。
上記のアプリはGitHubで公開していますので、宜しかったらダウンロードして実際に動かしてみてください。レポジトリは ViewFrameBoundsIndicatorで公開しています。
さて、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座標をずらして表示していることがイメージ出来ますでしょうか。
GitHubにも上げていますが、ViewControllerのソースを貼り付けておきます。
import UIKit class ViewController: UIViewController { @IBOutlet weak var boundXSlider: UISlider! @IBOutlet weak var boundYSlider: UISlider! @IBOutlet weak var rotationSlider: UISlider! @IBOutlet weak var detailTextView: UITextView! var targetView: UIView! var targetSubView: UIImageView! var frameLayer: CALayer! var boundsLayer: CALayer! @IBOutlet weak var clipBoundsSwitch: UISwitch! override func viewDidLoad() { super.viewDidLoad() targetView = UIView(frame: CGRect(x: 0, y: 0, width: 150, height: 200)) targetView.center = CGPoint(x: view.frame.width / 2, y: 200) targetView.backgroundColor = UIColor.whiteColor() targetView.layer.contentsScale = UIScreen.mainScreen().scale targetView.clipsToBounds = true // sub sub view targetSubView = UIImageView(frame: CGRect(x: 0, y: 0, width: 150, height: 200)) targetSubView.image = UIImage(named: "C789_unitoikuramaguro_TP_V.jpg") targetSubView.contentMode = UIViewContentMode.TopLeft targetSubView.layer.contentsScale = UIScreen.mainScreen().scale // add to sub view targetView.addSubview(targetSubView) // add to superview // view.addSubview(targetView) view.insertSubview(targetView, atIndex: 0) // layer of indicating frame border line frameLayer = CALayer() frameLayer.frame = targetView.frame frameLayer.backgroundColor = UIColor.clearColor().CGColor frameLayer.borderColor = UIColor.blueColor().CGColor frameLayer.borderWidth = 2.0 view.layer.addSublayer(frameLayer) // layer of indicating bounds border line boundsLayer = CALayer() boundsLayer.frame = targetView.bounds boundsLayer.backgroundColor = UIColor.clearColor().CGColor boundsLayer.borderColor = UIColor.greenColor().CGColor boundsLayer.borderWidth = 2.0 targetView.layer.addSublayer(boundsLayer) // information detailTextView.text = "frame: \(targetView.frame)\nbounds: \(targetView.bounds)\ncenter: \(targetView.center)" print("targetView frame : \(targetView.frame)") print("targetView bounds: \(targetView.bounds)") print("targetView center: \(targetView.center)") // slider handler settings boundXSlider.addTarget(self, action: #selector(changeBoundsHandler), forControlEvents: UIControlEvents.ValueChanged) boundYSlider.addTarget(self, action: #selector(changeBoundsHandler), forControlEvents: UIControlEvents.ValueChanged) rotationSlider.addTarget(self, action: #selector(changeBoundsHandler), forControlEvents: UIControlEvents.ValueChanged) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } func changeBoundsHandler(sender: AnyObject) { if boundXSlider == sender as! NSObject { // x targetView.bounds.origin.x = (targetSubView.image!.size.width - targetView.bounds.width) * CGFloat(boundXSlider.value) / 2 } else if boundYSlider == sender as! NSObject { // y targetView.bounds.origin.y = (targetSubView.image!.size.height - targetView.bounds.height) * CGFloat(boundYSlider.value) / 2 } else if rotationSlider == sender as! NSObject { // rotation let transform = CGAffineTransformMakeRotation(2 * CGFloat(M_PI) * CGFloat(rotationSlider.value)) targetView.transform = transform } // update layer of frame frameLayer.frame = targetView.frame boundsLayer.frame = targetView.bounds // information detailTextView.text = "frame: \(targetView.frame)\nbounds: \(targetView.bounds)\ncenter: \(targetView.center)" } @IBAction func changeSwitchHandler(sender: AnyObject) { targetView.clipsToBounds = clipBoundsSwitch.on } }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 件のコメント :
コメントを投稿