1

This must be something really simple, and my basic math knowledge may be lacking. This is clear (from this question):

View's frame determines its location in superview. View's bounds determines its subviews locations. That means, if you change view's bounds, its location won't be changed, but all of its subviews location will be changed.

The view controller, after starting a Single View App:

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let v1 = UIView(frame: CGRect(x: 100, y: 100, width: 200, height: 300))
        v1.backgroundColor = UIColor.blue

        let v2 = UIView(frame: v1.bounds.insetBy(dx: 50, dy: 50))
        v2.backgroundColor = UIColor.red

        self.view.addSubview(v1)
        v1.addSubview(v2)
    }

Checking on the LLDB console, this is completely clear too:

(lldb) p v1.frame
(CGRect) $R0 = (origin = (x = 100, y = 100), size = (width = 200, height = 300))
(lldb) p v1.bounds
(CGRect) $R1 = (origin = (x = 0, y = 0), size = (width = 200, height = 300))

(lldb) p v2.frame
(CGRect) $R2 = (origin = (x = 50, y = 50), size = (width = 100, height = 200))
(lldb) p v2.bounds
(CGRect) $R3 = (origin = (x = 0, y = 0), size = (width = 100, height = 200))

A red square positioned in the middle of a blue square

Adding v1.bounds.origin.x += 50 (or v1.bounds.origin.x = 50 for that matter) after v1.addSubview(v2) results in:

(lldb) p v1.frame
(CGRect) $R0 = (origin = (x = 100, y = 100), size = (width = 200, height = 300))
(lldb) p v1.bounds
(CGRect) $R1 = (origin = (x = 50, y = 0), size = (width = 200, height = 300))

(lldb) p v2.frame
(CGRect) $R2 = (origin = (x = 50, y = 50), size = (width = 100, height = 200))
(lldb) p v2.bounds
(CGRect) $R3 = (origin = (x = 0, y = 0), size = (width = 100, height = 200))

The LLDB console output still fits in with my current understanding, but then this is how it is rendered:

Red square lined up with the left wall of the blue square

Why? Tried to reason about it (see below) and I understand that the views' coordinate systems are relative to each other, but if 50 is added to v1's origin.x, the the subviews' effective frame.origin is supposed to be (x=50+50, y=0).

toraritte
  • 6,300
  • 3
  • 46
  • 67

2 Answers2

2

I found a satisfying answer in Matt Neuburg's Programming iOS 11 book with a similar example:

/* ... */
let v2 = UIView(frame:v1.bounds.insetBy(dx: 10, dy: 10))
/* ... */
v1.bounds.origin.x += 10
v1.bounds.origin.y += 10

Nothing happens to the superview’s size or position. But the subview has moved up and to the left so that it is flush with its superview’s top-left corner. Basically, what we’ve done is to say to the superview, “Instead of calling the point at your upper left (0.0,0.0), call that point (10.0,10.0).” Because the subview’s frame origin is itself at (10.0,10.0), the subview now touches the superview’s top-left corner. The effect of changing a view’s bounds origin may seem directionally backward — we increased the superview’s origin in the positive direction, but the subview moved in the negative direction — but think of it this way: a view’s bounds origin point coincides with its frame’s top left.

Therefore it seems modifying the origin is more like a mapping operation than a coordinate system transformation. This would also explain why the results are the same for += 50 and = 50.

enter image description here

toraritte
  • 6,300
  • 3
  • 46
  • 67
0

By adjusting the bounds' origin.x of v1, you are expanding the origin beyond the visible rectangle. (This is how a UIScrollView works.)

If you instead modify the frame's origin.x, you will, I believe, see results more in line with your expectations.

Joshua Kaden
  • 1,210
  • 11
  • 16