What is the difference between frame and bounds?
TL;DR
frame is where the view sits inside its superview.
bounds is the view's rectangle in its own coordinate space. Its origin is usually (0, 0).
Use frame to place a view. Use bounds to draw or lay out things inside it.
A simple example
let parent = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
let child = UIView(frame: CGRect(x: 50, y: 100, width: 120, height: 80))
parent.addSubview(child)
print(child.frame) // (50.0, 100.0, 120.0, 80.0) -> position in parent
print(child.bounds) // (0.0, 0.0, 120.0, 80.0) -> its own space
print(child.center) // (110.0, 140.0) -> also in parent space| Property | Coordinate space | Typical use |
|---|---|---|
frame | Superview | Position and size a view |
bounds | The view itself | Lay out subviews, draw content |
center | Superview | Move a view, works well with transforms |
Transforms change the frame, not the bounds
When you rotate or scale a view, bounds stays the same.
The frame becomes the smallest box that contains the rotated view.
child.transform = CGAffineTransform(rotationAngle: .pi / 4)
print(child.bounds) // still (0, 0, 120, 80)
print(child.frame) // bigger box, around 141 x 141Apple says frame is undefined when the transform is not identity.
So with transforms, use bounds and center, not frame.
Changing bounds.origin scrolls the content
bounds.origin says which part of the view's own space is visible.
If you move it, all subviews move the other way.
This is exactly how UIScrollView works.
let scrollView = UIScrollView()
scrollView.contentOffset = CGPoint(x: 0, y: 200)
print(scrollView.bounds.origin) // (0, 200)So contentOffset is just bounds.origin.
Converting between spaces
To compare points from different views, convert them first.
let pointInWindow = child.convert(CGPoint.zero, to: nil) // nil = window
let rectInParent = child.convert(child.bounds, to: parent) // equals child.frameWhich one to use in layoutSubviews
Lay out children using your own bounds. Your frame is in your parent's space, so it is the wrong reference.
override func layoutSubviews() {
super.layoutSubviews()
iconView.frame = CGRect(x: 0, y: 0, width: bounds.height, height: bounds.height)
}Common mistakes
- Using
self.frameto lay out subviews. If the view is not at(0, 0)in its parent, everything shifts. - Reading
frameafter a rotation transform. - Reading sizes in
viewDidLoad. Auto Layout has not run yet.
Common follow-up questions
- When can
bounds.originbe non-zero? In scroll views, or when you set it yourself to shift content. - Can
frameandboundssizes differ? Yes, when the view has a scale or rotation transform. - What does
centeruse? The superview's coordinate space, likeframe. - How do you get a view's position on screen? Use
convert(_:to: nil)to get window coordinates. - Does SwiftUI have frame and bounds? It has
.frame()for size andGeometryReaderoronGeometryChangeto read size and coordinate spaces.