[UIKit] Frame vs Bounds

팔랑이·2025년 4월 19일
0

iOS/Swift

목록 보기
69/71

UIView에는 frame, bounds, center라는 computed property가 있다.

center는 뭐 frame과 bounds의 중간점이기 때문에... frame과 bounds 두 개를 비교하여 작성할 예정이다.

UIView.frame vs UIView.bounds

우선 둘 다 UIView의 프로퍼티이며, frame과 bounds는 영역을 나타내는 것이기 때문에 CGRect로 값을 받는다.

참고로 CGPoint, CGSize, CGRect는 다음과 같은 Struct이다.

CGPointCGSizeCGRect

frame과 bounds는 둘다 CGRect를 받고, 이 CGRect는 위와 같이 size:CGsizeorigin:CGPoint를 받는다.

같은 구조체를 받지만 frame과 bounds에서 두 가지가 확실히 다르게 쓰이기 때문에, 그 차이를 명확하게 짚고 정리해보도록 한다.

width & height 비교

사진이 이해하기 쉬우니 사진으로 설명하고 빠르게 넘어가기.

다음과 같은 그림에서, UIView.frame과 UIView.bounds의 width와 height는 각각 234, 181로 같다.

UIView.frameUIView.bounds

반면 다음과 같이 뷰를 회전했을 때는,

UIView.frameUIView.bounds

UIView.frame는 저 회전한 뷰 전체를 감싸는 사각형 영역이라고 생각하면 되고, UIView.frame의 width와 height가 각각 268, 231로 나타난다.

반면, UIView.bounds는 뷰를 감싸는 프레임이 아닌 뷰 자체의 영역을 나타낸다. 사진과 같이 UIView.bounds의 width와 height는 그대로 234, 181이 된다.

origin 비교

차이를 먼저 말하자면, UIView.frame.origin부모 뷰를 기준으로 계산되고, UIView.bounds.origin자기 자신을 기준으로 계산된다.

frame.origin

: 부모 뷰를 기준으로 계산된다.

다음과 같이 rootView, rootView의 서브뷰 view1, view1의 서브뷰 view2가 있다.

UIKit에서, 어떤 view의 superView 또는 부모 뷰는 자신의 바로 상위 뷰이다. 즉, view1의 부모 뷰는 rootView, view2의 부모 뷰는 view1이다.

frame.origin은 부모 뷰를 기준으로 계산되므로, 부모 뷰의 시작점이 (0,0) 이라고 할 때,

  • view1의 frame.origin은 rootView의 시작점을 기준으로, 사진과 같이 (27,90) 이 되고,
  • view2의 frame.origin은 view1의 시작점을 기준으로, 사진과 같이 (27,56)이 된다.
view1의 frame.originview2의 frame.origin

여기서 view1의 frame.origin을 다음과 같이 (70,124)로 옮긴다고 해도, view2의 frame.origin은 그대로일 것이다. 부모 뷰인 view1의 원점보다 떨어진 거리를 비교하는 것이기 때문.

view1의 frame.originview2의 frame.origin

bounds.origin

이제 bounds.origin을 알아보자.

bounds.origin은 부모 뷰가 아닌, 자기 자신을 기준으로 계산한다.

아무것도 설정하지 않았을 때, bounds.origin은 (0,0)으로 초기화된다.
아래의 왼쪽 그림에서 왼쪽 위의 꼭지점이 모두 (0,0)으로 시작한다.

그런데 만약 view1의 bounds.origin을 (50,50)으로 바꾼다면?

view1 bounds.origin (0,0)view1 bounds.origin (50,50)

오른쪽 그림같이 된다.

bounds는 부모 뷰가 아닌 자신의 뷰를 기준으로, 내 뷰 안에서 어디를 기준으로 보여줄지 나타낸다. view2가 이동한 것처럼 보이지만, view1 상에서 카메라를 (0,0)에서 (50,50)으로 옮겼을 때 보이는 뷰를 보여준다고 이해하면 쉬울 것 같다.

카메라가 표시할 뷰는 view1의 frame 안에서 이루어지며, 범위 밖으로 나가는 서브뷰들을 보여주고 싶다면 clipToBounds = False로 설정하면 된다.

한 뷰 안에서 카메라(보는 시점)을 이동하는거다 보니, UIScrollView에서 자주 사용된다고 한다!

❗️ 참고로, 아까 위의 회전한 뷰에서는 origin이 각각 다음과 같다.

UIView.bounds, frame 활용 예제

어떤 버튼을 눌러서 화면이 넘어가는 것처럼 보이게 할 때, 네비게이션으로 이동하는게 아니라 일정한 영역에서 보여지는 뷰만 다르게 하고싶었다.

다른 방법이 떠오르지 않아서 이렇게 해 왔었는데, 일반적으로 이렇게 하는게 맞는지는..? 모르겠다

그림으로 구현하면 다음과 같이 될 것이다.

view1이 보이는 모습view2가 보이는 모습

이를 위해 stackButtonContainerView라는 컨테이너 뷰를 만들고,
그 안에 locationViewcategoryView를 frame으로 직접 배치했다.

  • .location 상태일 때는 locationView의 frame을 containerView.bounds에 맞춰서 보이게 하고
  • .category 상태일 때는 categoryView의 frame을 bounds에 맞춰서 전환되도록 처리한다.

두 개의 뷰를 딱 붙여놓으면, 화면의 margin = 20 이 있어야 하는 곳에 다른 뷰가 보일테니, 두 개의 뷰를 margin의 두 배인 40을 띄워 놓았다.

이렇게 하면 animation을 적용했을 때 안정적으로 좌우로 슬라이딩된다.

let containerBounds = self.stackButtonContainerView.bounds
switch step {
case .location:
	UIView.animate(withDuration: 0.3) {
    	self.locationView.frame = containerBounds
        self.categoryView.frame = containerBounds.offsetBy(dx: containerBounds.width + 40, dy: 0)
    }

	self.navigationItem.leftBarButtonItem = nil
                    
    case .category:
    UIView.animate(withDuration: 0.3) {
    	self.locationView.frame = containerBounds.offsetBy(dx: -(containerBounds.width + 40), dy: 0)
        self.categoryView.frame = containerBounds
    }
    
    self.navigationItem.leftBarButtonItem = self.backButton
}

결과물

근데 생각해보니..?

공부하고 나니 이거 그냥 spacing을 굳이 margin만큼 안주고 한 10만 주고, containerView에 clipToBounds = True 해도 되지 않나? 라는 의문

👉🏻 직접 적용해보니 똑같이 잘 된다! 어떻게 하든 실행상 효율에 별 차이는 없지 않을까? 싶긴 하지만...

profile
정체되지 않는 성장

0개의 댓글