ココア:フレームと境界の違いは何ですか?


587

UIViewとそのサブクラスはすべてプロパティframeandを持っていますbounds。違いは何ですか?



私にとって最も簡潔な説明はここにある:videos.raywenderlich.com/courses/99-scroll-view-school/lessons/...
ヴラド

1
2:30のビデオについては簡潔なものは何もありません。2時30分よりも1文または2文速く読むことができます。
dsjoerg

私にとっては、このように考えるとはるかに明確になります。フレーム=境界と位置
Serg

回答:


902

境界UIViewのは、ある矩形独自の座標系(0,0)からの相対位置(x、y)及びサイズ(幅、高さ)として表されます。

UIViewフレーム長方形であり、その中に含まれているスーパービューを基準とした位置(x、y)とサイズ(width、height)で表されます。

したがって、スーパービューの25,25(x、y)に配置された100x100(幅x高さ)のサイズのビューを想像してください。次のコードは、このビューの境界とフレームを出力します。

// This method is in the view controller of the superview
- (void)viewDidLoad {
    [super viewDidLoad];

    NSLog(@"bounds.origin.x: %f", label.bounds.origin.x);
    NSLog(@"bounds.origin.y: %f", label.bounds.origin.y);
    NSLog(@"bounds.size.width: %f", label.bounds.size.width);
    NSLog(@"bounds.size.height: %f", label.bounds.size.height);

    NSLog(@"frame.origin.x: %f", label.frame.origin.x);
    NSLog(@"frame.origin.y: %f", label.frame.origin.y);
    NSLog(@"frame.size.width: %f", label.frame.size.width);
    NSLog(@"frame.size.height: %f", label.frame.size.height);
}

そして、このコードの出力は次のとおりです。

bounds.origin.x: 0
bounds.origin.y: 0
bounds.size.width: 100
bounds.size.height: 100

frame.origin.x: 25
frame.origin.y: 25
frame.size.width: 100
frame.size.height: 100

したがって、どちらの場合でも、境界とフレームのどちらを見ているかに関係なく、ビューの幅と高さが同じであることがわかります。異なるのは、ビューのX、Y位置です。境界の場合、x座標とy座標は0,0にあります。これらの座標はビュー自体を基準にしているためです。ただし、フレームのx座標とy座標は、親ビュー内のビューの位置を基準にしています(以前は25、25でした)。

UIViewをカバーする素晴らしいプレゼンテーションもあります。フレームと境界の違いを説明するだけでなく、視覚的な例も示すスライド1〜20を参照してください。


37
したがって、境界のx、yは常に0,0になるわけではありません。なぜなら、オブジェクト自体の位置だからです。それともそうでないシナリオを教えてもらえますか?
mk12 2009

5
私の知る限りでは、境界の原点は常に0,0になる
蒋介石

77
実際、bounds.originは0,0以外の値になる場合があります。原点を移動/移動するには、setBoundsOrigin:を使用します。詳細については、「View Programming Guide for Cocoa」の「View Geometry」を参照してください。
Meltemi、2009

127
UIScrollViewは、ビュー内のさまざまなコンテンツ領域を表示するために境界をシフトできる例です。
ブラッドラーソン

92
小さなヒント-NSStringFromCGRectを使用すると、四角形をログに記録する時間を節約できます。
ベリリウム

647

簡潔な答え

frame = 親ビューの座標系を使用したビューの位置とサイズ

  • 重要:ビューを親に配置する

bounds = 独自の座標系を使用たビューの位置とサイズ

  • 重要:ビューのコンテンツまたはサブビューをそれ自体の中に配置する

詳細な回答

私が覚えて支援するためのフレームを、私は考える壁に額縁。額縁は、ビューの境界線のようなものです。私は壁のどこにでも絵を掛けることができます。同様に、親ビュー(スーパービューとも呼ばれる)内の任意の場所にビューを配置できます。親ビューは壁のようなものです。iOSの座標系の原点は左上です。ビューフレームのxy座標を(0、0)に設定することで、ビューをスーパービューの原点に配置できます。これは、壁の左上隅に画像をぶら下げるようなものです。右に移動するにはxを増やし、下に移動するにはyを増やします。

境界を思い出すのを助けるために、私は時々バスケットボールが境界からノックアウトされるバスケットボールコートを考えます。あなたはバスケットボールコート全体にボールをドリブルしていますが、コート自体がどこにあるかは気にしていません。ジムや高校の外、家の前などです。それは問題ではありません。あなたはバスケットボールをしたいだけです。同様に、ビューの境界の座標系は、ビュー自体のみを考慮します。ビューが親ビューのどこにあるかについては何も知りません。境界の原点(デフォルトではポイント(0、0))は、ビューの左上隅です。このビューにあるサブビューは、このポイントに関連してレイアウトされます。それはバスケットボールをコートの左前隅に持っていくようなものです。

フレームと境界を比較しようとすると、混乱が生じます。しかし、実際は最初のように悪くはありません。理解を助けるためにいくつかの写真を使用しましょう。

フレームと境界

左側の最初の画像には、親ビューの左上にあるビューがあります。黄色の長方形はビューのフレームを表します。右側には再びビューが表示されますが、今回は親ビューは表示されていません。これは、境界が親ビューを認識していないためです。緑の長方形はビューの境界を表します。両方の画像の赤い点は、フレームまたは境界の原点を表しています。

Frame
    origin = (0, 0)
    width = 80
    height = 130

Bounds 
    origin = (0, 0)
    width = 80
    height = 130

ここに画像の説明を入力してください

したがって、フレームと境界はその画像でまったく同じでした。それらが異なる例を見てみましょう。

Frame
    origin = (40, 60)  // That is, x=40 and y=60
    width = 80
    height = 130

Bounds 
    origin = (0, 0)
    width = 80
    height = 130

ここに画像の説明を入力してください

したがって、フレームのxy座標を変更すると、親ビュー内でフレームが移動することがわかります。ただし、ビュー自体の内容はまったく同じに見えます。境界は何かが違うということを知りません。

これまでは、フレームと境界の幅と高さはまったく同じでした。ただし、それは常に正しいとは限りません。ビューを時計回りに20度回転するとどうなるか見てください。(回転は変換を使用して行われます。詳細については、ドキュメント、およびこれらのビューレイヤーの例を参照してください。)

Frame
    origin = (20, 52)  // These are just rough estimates.
    width = 118
    height = 187

Bounds 
    origin = (0, 0)
    width = 80
    height = 130

ここに画像の説明を入力してください

境界がまだ同じであることがわかります。彼らはまだ何かが起こったことを知りません!ただし、フレーム値はすべて変更されています。

これで、フレームと境界の違いが少しわかりやすくなりましたね。おそらくフレームと境界を理解していないという記事では、ビューフレームを次のように定義しています。

...そのビューに適用されるすべての変換を含む、その親の座標系に関するそのビューの最小の境界ボックス。

ビューを変換すると、フレームが未定義になることに注意することが重要です。したがって、実際には、上の画像の回転した緑の境界線の周りに描いた黄色のフレームは実際には存在しません。つまり、回転、拡大縮小、またはその他の変換を行う場合は、フレーム値を使用しないでください。ただし、境界値は引き続き使用できます。アップルのドキュメントは警告します:

重要:ビューのtransformプロパティに恒等変換が含まれていない場合、そのビューのフレームは未定義であり、自動サイズ変更動作の結果も同様です。

むしろ、自動サイズ変更については残念ですが...できることはあります。

アップルのドキュメントは次のように述べています:

transformビューのプロパティを変更すると、すべての変換はビューの中心点を基準にして実行されます。

そのため、変換が完了した後で親内でビューを移動する必要がある場合は、view.center座標を変更することで実行できます。同様にframecenter親ビューの座標系を使用しています。

さて、回転を取り除き、境界に焦点を合わせましょう。これまでのところ、境界の原点は常に(0、0)のままです。ただし、その必要はありません。ビューに、一度に表示するには大きすぎる大きなサブビューがある場合はどうなりますか?UIImageView大きな画像で作ります。これも上から2番目の画像ですが、今回はビューのサブビューのコンテンツ全体がどのようになるかを確認できます。

Frame
    origin = (40, 60)
    width = 80
    height = 130

Bounds 
    origin = (0, 0)
    width = 80
    height = 130

ここに画像の説明を入力してください

画像の左上隅のみがビューの境界内に収まります。境界の原点座標を変更するとどうなるか見てみましょう。

Frame
    origin = (40, 60)
    width = 80
    height = 130

Bounds 
    origin = (280, 70)
    width = 80
    height = 130

ここに画像の説明を入力してください

フレームはスーパービューで移動していませんが、境界の長方形の原点がビューの別の部分から始まっているため、フレーム内のコンテンツが変更されています。これは、a UIScrollViewとそのサブクラス(a など)の全体的な考え方UITableViewです。詳細については、UIScrollViewについてを参照してください。

フレームを使用する場合と境界を使用する場合

frameは親ビュー内のビューの位置を関連付けるため、幅を変更したり、ビューと親ビューの上部との間の距離を見つけたりするなど、外向きの変更を行うときに使用します。

描画やビュー内のサブビューの配置など、内側の変更boundsを行うときにを使用します。ビューに変換を行った場合は、境界を使用してビューのサイズを取得します。

さらなる研究のための記事:

Appleドキュメント

関連するStackOverflowの質問

その他の資料

自分で練習する

上記の記事を読むことに加えて、テストアプリを作成するのに非常に役立ちます。同様のことを試してみてください。(このビデオコースからアイデアを得ましたが、残念ながら無料ではありません。)

ここに画像の説明を入力してください

あなたの参照のためのコードはここにあります:

import UIKit

class ViewController: UIViewController {


    @IBOutlet weak var myView: UIView!

    // Labels
    @IBOutlet weak var frameX: UILabel!
    @IBOutlet weak var frameY: UILabel!
    @IBOutlet weak var frameWidth: UILabel!
    @IBOutlet weak var frameHeight: UILabel!
    @IBOutlet weak var boundsX: UILabel!
    @IBOutlet weak var boundsY: UILabel!
    @IBOutlet weak var boundsWidth: UILabel!
    @IBOutlet weak var boundsHeight: UILabel!
    @IBOutlet weak var centerX: UILabel!
    @IBOutlet weak var centerY: UILabel!
    @IBOutlet weak var rotation: UILabel!

    // Sliders
    @IBOutlet weak var frameXSlider: UISlider!
    @IBOutlet weak var frameYSlider: UISlider!
    @IBOutlet weak var frameWidthSlider: UISlider!
    @IBOutlet weak var frameHeightSlider: UISlider!
    @IBOutlet weak var boundsXSlider: UISlider!
    @IBOutlet weak var boundsYSlider: UISlider!
    @IBOutlet weak var boundsWidthSlider: UISlider!
    @IBOutlet weak var boundsHeightSlider: UISlider!
    @IBOutlet weak var centerXSlider: UISlider!
    @IBOutlet weak var centerYSlider: UISlider!
    @IBOutlet weak var rotationSlider: UISlider!

    // Slider actions
    @IBAction func frameXSliderChanged(sender: AnyObject) {
        myView.frame.origin.x = CGFloat(frameXSlider.value)
        updateLabels()
    }
    @IBAction func frameYSliderChanged(sender: AnyObject) {
        myView.frame.origin.y = CGFloat(frameYSlider.value)
        updateLabels()
    }
    @IBAction func frameWidthSliderChanged(sender: AnyObject) {
        myView.frame.size.width = CGFloat(frameWidthSlider.value)
        updateLabels()
    }
    @IBAction func frameHeightSliderChanged(sender: AnyObject) {
        myView.frame.size.height = CGFloat(frameHeightSlider.value)
        updateLabels()
    }
    @IBAction func boundsXSliderChanged(sender: AnyObject) {
        myView.bounds.origin.x = CGFloat(boundsXSlider.value)
        updateLabels()
    }
    @IBAction func boundsYSliderChanged(sender: AnyObject) {
        myView.bounds.origin.y = CGFloat(boundsYSlider.value)
        updateLabels()
    }
    @IBAction func boundsWidthSliderChanged(sender: AnyObject) {
        myView.bounds.size.width = CGFloat(boundsWidthSlider.value)
        updateLabels()
    }
    @IBAction func boundsHeightSliderChanged(sender: AnyObject) {
        myView.bounds.size.height = CGFloat(boundsHeightSlider.value)
        updateLabels()
    }
    @IBAction func centerXSliderChanged(sender: AnyObject) {
        myView.center.x = CGFloat(centerXSlider.value)
        updateLabels()
    }
    @IBAction func centerYSliderChanged(sender: AnyObject) {
        myView.center.y = CGFloat(centerYSlider.value)
        updateLabels()
    }
    @IBAction func rotationSliderChanged(sender: AnyObject) {
        let rotation = CGAffineTransform(rotationAngle: CGFloat(rotationSlider.value))
        myView.transform = rotation
        updateLabels()
    }

    private func updateLabels() {

        frameX.text = "frame x = \(Int(myView.frame.origin.x))"
        frameY.text = "frame y = \(Int(myView.frame.origin.y))"
        frameWidth.text = "frame width = \(Int(myView.frame.width))"
        frameHeight.text = "frame height = \(Int(myView.frame.height))"
        boundsX.text = "bounds x = \(Int(myView.bounds.origin.x))"
        boundsY.text = "bounds y = \(Int(myView.bounds.origin.y))"
        boundsWidth.text = "bounds width = \(Int(myView.bounds.width))"
        boundsHeight.text = "bounds height = \(Int(myView.bounds.height))"
        centerX.text = "center x = \(Int(myView.center.x))"
        centerY.text = "center y = \(Int(myView.center.y))"
        rotation.text = "rotation = \((rotationSlider.value))"

    }

}

境界の幅と高さが冗長であり、「origin」プロパティで十分だったように見えます(Flashの場合と同様)。
Ian Warburton 2017

「ものを描画したり、ビュー内にサブビューを配置するような」境界を使用します。つまり、2つのボタンを持つviewControllerがある場合、viewControllerのビューの「境界」を使用してボタンを配置する必要がありますか。私はいつもビューのフレームを使用してきました...
ハニー

@ハニー、私は昨年Androidに取り組んでいるので、iOSのスキルに少し錆びています。ビューコントローラーのルートビューが画面いっぱいに表示されるため、フレームと境界は同じになるはずです。
Suragch

5
作成したgithub.com/maniramezan/FrameVsBounds.gitを理解する上で、より良い助けるためにここに挙げた例のアプリに基づいてレポboundsframeビューの。
manman 2018年

1
@IanWarburtonビューが回転すると、境界の幅と高さがフレームの幅と高さに一致しないため、冗長ではありません。
エドワードブレイ

43

以下のコードを実行してみてください

- (void)viewDidLoad {
    [super viewDidLoad];
    UIWindow *w = [[UIApplication sharedApplication] keyWindow];
    UIView *v = [w.subviews objectAtIndex:0];

    NSLog(@"%@", NSStringFromCGRect(v.frame));
    NSLog(@"%@", NSStringFromCGRect(v.bounds));
}

このコードの出力は次のとおりです。

ケースのデバイスの向きは縦向きです

{{0, 0}, {768, 1024}}
{{0, 0}, {768, 1024}}

ケースのデバイスの向きは横向きです

{{0, 0}, {768, 1024}}
{{0, 0}, {1024, 768}}

明らかに、フレームと境界の違いを見ることができます


2
これはもう本当ではありません(iOS 8)
JulianKrólSep


13

フレームは、とのUIViewを定義する矩形であり、そのスーパーに対して

RECT境界ことを定義する値の範囲であるNSViewの座標系です。

つまり、この四角形のすべてが実際にUIViewに表示されます。


10

フレームは、スーパービューの座標系でのビューの原点(左上隅)とサイズです。つまり、フレームの原点を変更することにより、スーパービューでビューを移動します。一方、境界は、そのサイズと原点です。独自の座標系なので、デフォルトでは境界の原点は(0,0)です。

ほとんどの場合、フレームと境界は合同ですが、たとえば、フレーム((140,65)、(200,250))と境界((0,0)、(200,250))のビューがあり、ビューが傾斜している場合右下隅に立つように、境界は((0,0)、(200,250))のままですが、フレームはそうではありません。

ここに画像の説明を入力してください

フレームはビューをカプセル化/囲む最小の長方形になるため、(写真のように)フレームは((140,65)、(320,320))になります。

別の違いは、たとえば、境界が((0,0)、(200,200))のスーパービューがあり、このスーパービューにフレームが((20,20)、(100,100))のサブビューがあり、スーパービューの境界を変更した場合です。 ((20,20)、(200,200))に変更すると、subViewフレームは((20,20)、(100,100))のままですが、(20,20)によってオフセットされます。これは、そのスーパービュー座標系が(20、 20)。

これが誰かの役に立つことを願っています。


最後の段落に関して: subViewフレームはそれに関連していsuperViewます。superView境界を何かに変更しても、subView原点はそれと一致しませんsuperView
staticVoidMan 2016

5

SuperViewを基準にしてフレームを作成し、NSViewを基準にしてバウンドします。

例:X = 40、Y =60。3つのビューも含まれます。この図は、明確なアイデアを示しています。

フレーム

バウンド


4

5セント追加します。

フレームは、ビューの親ビューが親ビュー内に配置するために使用されます。

Boundsは、ビュー自体が自身のコンテンツを配置するために使用されます(スクロールビューがスクロール中に行うように)。clipsToBoundsも参照してください。境界は、ビューのコンテンツを拡大/縮小するためにも使用できます。

類推:
フレーム〜TV画面の
境界〜カメラ(ズーム、移動、回転)


2

上記の回答は、境界とフレームの違いを非常によく説明しています。

境界:独自の座標系によるビューのサイズと位置。

フレーム:SuperViewを基準にしたビューサイズと位置。

次に、境界の場合、X、Yが常に「0」になるという混乱があります。これは真実ではありません。これは、UIScrollViewとUICollectionViewでも理解できます。

境界のx、yが0でない
場合。UIScrollViewがあると仮定します。ページネーションを実装しました。UIScrollViewには3つのページがあり、そのContentSizeの幅は画面の幅の3倍です(ScreenWidthが320であると想定)。高さは一定です(200と想定)。

scrollView.contentSize = CGSize(x:320*3, y : 200)

3つのUIImageViewをサブビューとして追加し、フレームのx値をよく見てください

let imageView0 = UIImageView.init(frame: CGRect(x:0, y: 0 , width : scrollView.frame.size.width, height : scrollView.frame.size.height))
let imageView1 :  UIImageView.init( frame: CGRect(x:320, y: 0 , width : scrollView.frame.size.width, height : scrollView.frame.size.height))
let imageView2 :  UIImageView.init(frame: CGRect(x:640, y: 0 , width : scrollView.frame.size.width, height : scrollView.frame.size.height))
scrollView.addSubview(imageView0)
scrollView.addSubview(imageView0)
scrollView.addSubview(imageView0)
  1. ページ0:ScrollViewが0ページの場合、境界は (x:0、y:0、幅:320、高さ:200)になります。

  2. ページ1:スクロールしてページ1に移動します。
    境界は(x:320、y:0、幅:320、高さ:200)になり ます。独自の座標系を基準にして言ったことを思い出してください。これで、ScrollViewの「Visible Part」の320が「x」になります。imageView1のフレームを見てください。

  3. ページ2:スクロールして、ページ2に移動します。 境界:(x:640、y:0、幅:320、高さ:200) もう一度、imageView2のフレームを確認します。

UICollectionViewの場合も同様です。collectionViewを見る最も簡単な方法は、それをスクロールして、その境界を印刷/ログに記録することです。


2

上記のすべての答えは正しく、これは私の見解です:

フレームと境界を区別するにはCONCEPTS開発者は以下を読む必要があります。

  1. スーパービュー(1つの親ビュー)を基準にして含まれます= FRAME
  2. 自身の座標系を基準にして、そのサブビューの位置を決定します= BOUNDS

「境界」は、座標が設定されているビューの位置であるような印象を与えるため、混乱を招きます。ただし、これらは関係にあり、フレーム定数に従って調整されます。

iPhone画面


1

フレームと境界の違いを示すもう1つの図。この例では:

  • View B のサブビューです View A
  • View B に移動しました x:60, y: 20
  • View B 回転した 45 degrees

ここに画像の説明を入力してください


0

フレームとバインド

  • X:0、Y:0、幅:400、高さ:400でビューを作成する場合、そのフレームと境界は同じです。
  • そのビューをX:400に移動すると、フレームにはその変更が反映されますが、境界には反映されません。境界はビュー自体のスペースに相対的であり、ビューの内部では何も変更されていないことに注意してください。
  • ビューを変換、たとえば回転または拡大すると、フレームはそれを反映するように変化しますが、境界は変化しません。ビューに関する限り、それは変化していません。
  • 境界を変更すると、境界の長方形の原点がビューの別の部分から始まるため、フレーム内のコンテンツが変更されます。

-1

frame =親ビューの座標系を使用したビューの位置とサイズ

bounds =独自の座標系を使用したビューの位置とサイズ

ビューは、フレーム長方形と境界長方形の2つの長方形を使用して、サイズと位置を追跡します。フレームの長方形は、スーパービューの座標系を使用して、スーパービュー内のビューの位置とサイズを定義します。境界長方形は、原点とスケーリングを含むビューのコンテンツを描画するときに使用される内部座標系を定義します。図2-1は、左側のフレームの長方形と右側の境界の長方形の関係を示しています。」

つまり、フレームはスーパービューのビューのアイデアであり、境界はビュー自体のアイデアです。ビューごとに1つずつ、複数の座標系を持つことは、ビュー階層の一部です。


図2-1はどこにありますか?
アレクサンダーマヤツキー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.