TL;DR
subview.frame = CGRect(origin: .zero, size: view.bounds.size)
と書く方が良い。
本文
あるUIViewに別のUIViewを同じサイズにして乗せたいケースはよくあると思います。
その際にframeの指定をどのように行うのかの選択肢があり、現場のコードでもいくつかの種類を見かけます。
~.frame = view.frame
だったり ~.frame = view.bounds
だったり...
view.bounds
を用いることが多いと思いますし自分もうそう書いていたのですが、改めてどのような指定方法が良いのか考えみようと思います。
sizeの指定にはframeではなくboundsを使う
frameは親viewの座標系における自分の場所を示すものであるのに対して、boundsは自分自身の座標系が基準となっています。
例えば以下のように子viewをtransformで回転させると、frameとboundsのsizeは違う値になります。
let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
let redView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
redView.backgroundColor = .red
view.addSubview(redView)
let blueView = UIView(frame: CGRect(x: 20, y: 20, width: 50, height: 50))
blueView.backgroundColor = .blue
redView.addSubview(blueView)
blueView.transform = CGAffineTransform(rotationAngle: 1)
blueView.frame // (x: 10.456, y: 10.456, width: 69.089, height: 69.089)
blueView.bounds // (x: 0, y: 0, width: 50, height: 50)
これは、傾いた四角形を表示するのに四角形自身のサイズ (width: 100, height: 100) よりも大きな領域が必要となったためです。
よって親viewからどう見えるかを示すframe.sizeよりも自分自身の大きさを示すbounds.sizeの値を使うのが良いでしょう。
originの指定は (x: 0, y: 0) にする
bounds.originは (x: 0, y: 0) 以外も指定できます。
(x: 0, y: 0) 以外を指定するとsubviewの位置が変化します。
例えば以下のようなviewを用意します。
let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
let redView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
redView.backgroundColor = .red
view.addSubview(redView)
let blueView = UIView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
blueView.backgroundColor = .blue
redView.addSubview(blueView)
このときの表示は以下になります。
ここでredViewのboundsのoriginを以下のように (-10, -10) に変更すると、表示はこのようになります。
redView.bounds = CGRect(x: -20, y: -20, width: 100, height: 100)
これは bounds.origin が (x: 0, y: 0) になる場所がsubviewの描画の起点になるため です。
つまり、ほとんどないとは思いますがbounds.originの値が (x: 0, y: 0) 以外に設定されて描画を調整しているという場合もあり得るということです。
なのでbounds.originの値が常に (x: 0, y: 0) になると想定せず、直接 (x: 0, y: 0) を指定した方が無難でしょう。
bounds.sizeを変化させてみると
少し蛇足ですがbounds.sizeの値を変えたときにどうなるか見てみます。
上記のコードでboundsのsizeを (width: 50, height: 50) にしてみましょう。
redView.bounds = CGRect(x: -20, y: -20, width: 50, height: 50)
redViewのframeは変化させていないにも関わらず描画サイズが変化しました。
frameのsizeではなくboundsのsizeの方が実際の描画サイズを表すという結果になりました。
結論
sizeの指定には親viewのbounds.size、originの指定には (x: 0, y: 0) を指定するのが良いことが分かりました。
CGPointで (x: 0, y: 0) を指定するときは .zero
と書けますのでSwiftのコードとしては以下のような書き方になります。
subview.frame = CGRect(origin: .zero, size: view.bounds.size)
参考
- https://stackoverflow.com/questions/8452730/when-would-a-uiviews-bounds-origin-not-be-0-0
- https://ashfurrow.com/blog/you-probably-dont-understand-frames-and-bounds/
環境
Xcode 11.2.1
Swift 5.1