なんとなく違いは分かっていましたが、いつも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 件のコメント :
コメントを投稿