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のソースを貼り付けておきます。

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
AD.

0 件のコメント :

コメントを投稿