Answer
The core idea
frame and bounds describe the same view, but from two different coordinate systems.
The short interview answer is: frame answers "where is this view in its superview?" and bounds answers "what is this view's own internal coordinate space?"
1. What frame represents
frame is the rectangle that the superview sees. Its origin is the view's position relative to the superview, and its size is how large the view appears in that parent coordinate system.
If a view's frame is (x: 40, y: 60, width: 80, height: 130), that means:
- the view starts 40 points from the parent's left edge
- the view starts 60 points from the parent's top edge
- the view occupies an 80 by 130 point rectangle in the parent
That is why frame is the property people reach for when placing a view on screen.
import UIKit
let card = UIView(frame: CGRect(x: 40, y: 60, width: 80, height: 130))
print(card.frame) // (40, 60, 80, 130) in the superview's coordinates2. What bounds represents
bounds is the rectangle that the view uses to describe itself. It is the view's own internal coordinate system, which is what drawing, hit-testing, and subview layout reason about.
By default, a view's bounds usually starts at (0, 0) and has the same size as the frame. That means a newly created view often starts with:
import UIKit
let card = UIView(frame: CGRect(x: 40, y: 60, width: 80, height: 130))
print(card.bounds) // (0, 0, 80, 130) in the view's own coordinatesThe important part is that bounds.origin is not "where the view sits in the parent." It is "where this view considers its own visible content to begin."
3. When bounds and frame line up
In the simplest case, they line up numerically:
- the view has no transform applied
bounds.origin == .zeroframe.size == bounds.size
That is the common "just created a view" scenario:
import UIKit
let card = UIView(frame: CGRect(x: 40, y: 60, width: 80, height: 130))
// Numerically:
// frame = (40, 60, 80, 130)
// bounds = (0, 0, 80, 130)The sizes match, but the meanings still differ:
frame.originis in the parent's coordinate systembounds.originis in the view's own coordinate system
So even when the width and height are equal, frame and bounds are still answering different questions.
4. When bounds and frame differ
There are three interview-important cases.
Move the view in its parent
If you change frame.origin or center, the view moves in the superview. The view's own internal content does not change, so bounds usually stays the same.
import UIKit
let card = UIView(frame: CGRect(x: 40, y: 60, width: 80, height: 130))
card.frame.origin.x = 140
// frame -> (140, 60, 80, 130)
// bounds -> (0, 0, 80, 130)Visually, the whole card moves to the right. Its internal coordinate system does not care that the parent now sees it somewhere else.
Scroll or offset the content
If you change bounds.origin, the view does not move in its parent. Instead, the visible portion of the content shifts inside the same outer rectangle.
That is the core idea behind UIScrollView: the viewport stays where it is, but the bounds origin changes to reveal a different part of the content.
import UIKit
let scrollView = UIScrollView(frame: CGRect(x: 20, y: 40, width: 200, height: 120))
scrollView.bounds.origin.y = 80
// frame -> still the same rectangle in the parent
// bounds -> now starts at y = 80 inside the scroll view's own content spaceVisually, it looks like the content moved upward inside the scroll view, even though the scroll view itself did not move.
Apply a transform
Transforms are the case where many interview answers become too casual. After a non-identity transform such as rotation or scaling, Apple says the view's frame is undefined and should be ignored.
In practice, when you inspect it, frame often looks like the smallest axis-aligned rectangle that encloses the transformed view. That is why developers say things like "the frame got bigger after rotation." But the interview-safe and production-safe answer is Apple's answer: do not rely on frame after transforms.
The view's bounds still describes its own logical internal size, so bounds remains the safer property for internal layout.
import UIKit
let card = UIView(frame: CGRect(x: 40, y: 60, width: 100, height: 60))
card.transform = CGAffineTransform(rotationAngle: .pi / 6)
// Do not reason from card.frame here.
// Use card.bounds for the view's own size and card.center for position.5. If I change one, what happens to the other?
Apple's docs are explicit about some of these relationships:
- setting
framechangescenterand updatesbounds.size - setting
bounds.sizeupdatesframe.size - setting
bounds.originchanges the internal coordinate system, not the superview position
6. When should you use frame and when should you use bounds?
Use frame when you are making an outward decision about the view relative to its parent:
- positioning the view in its superview
- checking how far it is from another parent-based coordinate
- doing simple layout when no transform is involved
Use bounds when you are making an inward decision about the view's own content:
- laying out subviews inside the view
- custom drawing in
draw(_:) - reasoning about visible content inside a scroll view
- reading the view's size when transforms are involved
One good interview sentence is: use frame to place the view in its parent, and use bounds to lay out or draw inside the view itself.
Why it matters
This is not just geometry trivia. Mixing them up causes real bugs.
A very common real-world mistake is using self.frame inside layoutSubviews() to position subviews. That code looks reasonable at first, but frame is in the parent's coordinate system, while subviews must be laid out in the view's own coordinate system.
Imagine a reusable card view placed at x = 40 inside a collection view cell. The title label should have 16 points of padding inside the card, but the code accidentally uses frame:
import UIKit
final class ProfileCardView: UIView {
private let titleLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(titleLabel)
}
override func layoutSubviews() {
super.layoutSubviews()
titleLabel.frame = frame.insetBy(dx: 16, dy: 12) // ❌ Wrong
}
}If the card's own frame is (40, 60, 200, 120), then frame.insetBy(dx: 16, dy: 12) becomes roughly (56, 72, 168, 96). But titleLabel.frame is interpreted inside ProfileCardView itself, so the label starts 56 points from the card's left edge instead of 16. The bug becomes even more confusing when the same card is reused in a different parent position, because the internal layout changes even though the card's content logic did not.
The correct code uses bounds, because bounds is the card's own local coordinate space:
import UIKit
final class ProfileCardView: UIView {
private let titleLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(titleLabel)
}
override func layoutSubviews() {
super.layoutSubviews()
titleLabel.frame = bounds.insetBy(dx: 16, dy: 12) // ✅ Correct
}
}The same rule applies in draw(_:): draw relative to bounds, not frame, because custom drawing also happens in the view's own coordinate system.
Interview angle
Walk the answer in this order: frame is the rectangle in the superview -> bounds is the rectangle in the view's own coordinate system -> in the simple case the sizes match -> moving changes frame, scrolling changes bounds.origin, and transforms make frame unsafe to trust -> use frame for parent-based layout and bounds for internal layout or drawing. Apple's docs for UIView.frame and UIView.bounds are the cleanest references for the exact rules.