Frame & Bounds

최완식·2022년 5월 20일
0

UIKit

목록 보기
19/33
post-thumbnail

맨날 헷갈리는 이것. 오늘은 뽑아버리겠다.

Frame

Super view의 좌표계를 기준으로 했을 때 본인의 좌표계를 말한다.

우리가 위치를 표시하기 위해서는 기준 좌표계가 있어야 한다. iPhone의 경우 좌상단에서 시작하여 x축은 오른쪽, y축은 아래로 가는 기본 좌표계를 갖는다.

이 근본 좌표계 위에서 우리는 요소들을 놓아야 하는데, 이 때 가장 기본이 되는 것이 frame이다. apple은 특정 요소를 우리가 만들 때, 해당 요소를 놓을 기준을 나의 부모로 잡았다. 즉, 나의 부모에 비해서 나의 위치가 어딘가?로 설정하는 것. 그리고 이 생각에 가장 맞는 요소가 frame이다.

이렇게 내가 놓고 싶은 요소의 위치를 파악할 때, 항상 부모 View에 Subview로 넣어주는 동작을 수행할 것이기 때문에, 이러한 사고는 사실 자연스럽다.

let parentView = UIView()
let childView = UIView()

parentView.addSubview(childView)
childView.frame = CGRect(x: 10, y: 10, width: 20, height: 40) // 부모뷰 기준으로 위치 선정

Rotate

회전의 경우에는 어떨지 궁금하여 돌려보았다.

----------
ViewA frame: (0.0, 0.0, 390.0, 844.0)
ViewA bounds: (0.0, 0.0, 390.0, 844.0)
ViewB frame: (100.0, 200.0, 200.0, 300.0)
ViewB bounds: (0.0, 0.0, 200.0, 300.0)
----------

여기까지는 방금 이해한 내용과 같다.

----------
ViewA frame: (0.0, 0.0, 390.0, 844.0)
ViewA bounds: (0.0, 0.0, 390.0, 844.0)
ViewB frame: (23.22330470336314, 173.2233047033631, 353.5533905932738, 353.55339059327383)
ViewB bounds: (0.0, 0.0, 200.0, 300.0)
----------

회전하게 되니 viewB의 frame이 변경되었다. 이는 변경되었을 때 사각형을 모두 포함하면서 최소인 사각형을 의미하는 것이 frame이라는 것을 알 수 있다.

이런식으로 감싼 사각형이 frame이다.

Bounds

자신만의 고유한 좌표계

그렇다면 저 실제 사각형에 관련된 값을 얻을 수는 없을까? 그것이 bounds이다. 설명을 보면 자신만의 고유한 좌표계를 나타내기 때문에, 현재 회전된 상황에서 bounds의 좌표계를 그려보면 아래와 같다.

----------
ViewA frame: (0.0, 0.0, 390.0, 844.0)
ViewA bounds: (0.0, 0.0, 390.0, 844.0)
ViewB frame: (23.22330470336314, 173.2233047033631, 353.5533905932738, 353.55339059327383)
ViewB bounds: (0.0, 0.0, 200.0, 300.0)
----------

실제로 회전된 시뮬레이터에서 값을 찍어보면, 회전됐음에도 불구하고 ViewB의 Bounds는 동일한 것을 알 수 있다.

Bounds를 바꿔보자

viewA(노랑)에 subview로 viewB(파랑)가 들어가 있는 상황에서 viewA의 bounds를 바꿔보겠다.

self.viewA.bounds.origin = CGPoint(x: 100, y: 200)

왜 파란색 뷰가 위로 올라갔을까? 우하단으로 가야되는 것 아닌가? 라고 생각한 사람이 있다면 잘왔다. bounds의 이동은 좌표계의 이동이다.

좌표계가 (100, 200) 움직이고, 실제 보이는 화면은 이 움직인 좌표계를 기반으로 해서 보여진다. 그렇기 때문에 가만히 있는 viewB가 우리 눈에는 좌상단으로 간것 처럼 보이는 것이다!!

Whole Code

import UIKit

class FrameBoundsViewController: UIViewController {

    let viewA: UIView = {
        let view = UIView()
        view.backgroundColor = .yellow
        return view
    }()

    let viewB: UIView = {
        let view = UIView()
        view.backgroundColor = .blue
        return view
    }()

    let rotateButton: UIButton = {
        let button = UIButton()
        button.setTitle("45도 회전", for: .normal)
        button.setTitleColor(UIColor.black, for: .normal)
        return button
    }()

    let debugButton: UIButton = {
        let button = UIButton()
        button.setTitle("DEBUG", for: .normal)
        button.setTitleColor(UIColor.black, for: .normal)
        return button
    }()

    let boundsButton: UIButton = {
        let button = UIButton()
        button.setTitle("ViewA Bounds +10, +10", for: .normal)
        button.setTitleColor(UIColor.black, for: .normal)
        return button
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        self.viewA.frame = CGRect(origin: .zero, size: self.view.frame.size)
        self.viewB.frame = CGRect(origin: CGPoint(x: 100, y: 200), size: CGSize(width: 200, height: 300))
        self.rotateButton.frame = CGRect(x: 20, y: 600, width: 200, height: 40)
        self.debugButton.frame = CGRect(x: 220, y: 600, width: 200, height: 40)
        self.boundsButton.frame = CGRect(x: 20, y: 700, width: 400, height: 40)

        self.view.addSubview(self.viewA)
        self.view.addSubview(self.rotateButton)
        self.view.addSubview(self.debugButton)
        self.viewA.addSubview(self.viewB)

        self.rotateButton.addTarget(self, action: #selector(self.rotateRectangle), for: .touchUpInside)
        self.debugButton.addTarget(self, action: #selector(self.debug), for: .touchUpInside)

        self.viewA.bounds.origin = CGPoint(x: 100, y: 200)
    }

    @objc func rotateRectangle() {
        UIView.animate(withDuration: 1) {
            self.viewB.transform = CGAffineTransform(rotationAngle: .pi/4)
        }
    }

    @objc func debug() {
        print("----------")
        print("ViewA frame: \(self.viewA.frame)")
        print("ViewA bounds: \(self.viewA.bounds)")
        print("ViewB frame: \(self.viewB.frame)")
        print("ViewB bounds: \(self.viewB.bounds)")
        print("----------")
    }

}

마무리

간단하게 frame, bounds에 대해서 알아보았다. bounds는 실제 눈에 보이는 좌표계가 이동하는 것임을 기억하자. 좌표계가 움직이기 때문에 실제 눈에 보이는 녀석은 정반대로 움직이는 것처럼 보이게 된다. 끝!

Reference

틀린 정보나 궁금한 점이 있다면 언제든 Twitter로 연락주세요! 감사합니다.

profile
Goal, Plan, Execute.

0개의 댓글