우선 View의 Layout을 잡기전에 View가 어떠한 방식으로 설정되고 그려지는지 먼저 고민해야합니다.
iOS에서 View를 그리는 부분은 위 그림과 같이 크게 3가지 phase를 나눕니다
제약조건
을 설정layout
draw
)위와 같이 예시가 있으면
---UPDATE CONSTRAINTS---
ThirdV updateConstraints
SecondV updateConstraints
SecondOfSecondV updateConstraints
FirstV updateConstraints
//updateViewConstraints in VC
---LAYOUT SUBVIEWS---
FirstV layoutSubviews
SecondOfSecondV layoutSubviews
SecondV layoutSubviews
ThirdV layoutSubviews
---DRAW RECT---
FirstV draw-rect
SecondOfSecondV draw-rect
SecondV draw-rect
ThirdV draw-rect
View의 계층구조 상에서 Left -> Root순(BotttomUp)으로 제약 설정
나머지는 Root -> Left순(TopDown)으로 Layout
되고 Draw
된다.
이렇게 View가 어떻게 그려지는지 알았으므로
제일 기본적인 개념으로 좌표 시스템에 대해 학습해야합니다.
iOS에서 뷰 좌표 시스템은 각 뷰가 자체적으로 가지는 2D 좌표 공간을 의미
화면 왼쪽 상단을 원점
(0, 0)
으로 하는 직교 좌표계
- x축: 오른쪽으로 갈수록 값이 커짐
- y축: 아래쪽으로 갈수록 값이 커짐
- 독립성 : 각 뷰는 자신의 좌표 시스템을 가지며, 부모 뷰에 대해 상대적으로 위치가 결정됩니다. 즉, 뷰가 중첩되어 있는 경우, 자식 뷰의 좌표는 부모 뷰의 좌표 시스템을 기준으로 계산됩니다.
frame
은 UIView
클래스의 주요 속성 중 하나로, 뷰의 위치와 크기를 정의합니다. frame
은 슈퍼뷰(superview
)의 좌표 시스템에서 뷰의 사각형 영역을 나타냅니다.
frame의 구조
frame
은CGRect
타입이며,CGRect
는 다음과 같은 속성으로 구성됩니다 :
- origin : 뷰의 원점을 나타내는
CGPoint
구조체입니다.x
와y
좌표를 포함하며, 부모 뷰의 좌표 시스템을 기준으로 합니다.- size : 뷰의 크기를 나타내는
CGSize
구조체입니다.width
와height
를 포함합니다.frame의 구성 요소
- origin : 뷰의 왼쪽 상단 모서리의 좌표로, 부모 뷰의 좌표 시스템을 기준으로 설정됩니다.
- size : 뷰의 너비와 높이를 정의하며, 뷰의 실제 크기를 결정합니다.
특징
- View가 많아지면 다른 모델에(특히 해상도가 달라지면) 대해서도 하나한 관리해 주어야 하기에 개발자 입장에서 중노동이 될 수있음.
- AutoResizing Masks는 SuperView에 대해(외부 요인) 자동으로 사이즈를 맞추어 주지만 이는 극히 일부에 도움이 될 뿐 모든 문제를 해결해 주지는 않는다.
다음은 UIView
의 frame
을 사용하여 뷰를 배치하는 간단한 예제입니다.
let parentView = UIView(frame: CGRect(x: 50, y: 50, width: 300, height: 500))
parentView.backgroundColor = .lightGray
let childView = UIView(frame: CGRect(x: 50, y: 50, width: 100, height: 150))
childView.backgroundColor = .blue
parentView.addSubview(childView)
self.view.addSubview(parentView)
이 예제에서:
parentView
: main 아래의 슈퍼뷰로, 메인으로 부터 (50, 50)
의 원점을 가지며, 크기는 300x500
입니다.childView
: 자식 뷰로, parentView
의 좌표 시스템에서 (50, 50)
위치에 놓이며, 크기는 100x150
입니다. 즉, childView
의 원점은 parentView
의 왼쪽 상단 모서리에서 오른쪽으로 50포인트, 아래쪽으로 50포인트 떨어진 위치에 있습니다.frame
외에도 UIView에는 뷰의 위치와 크기를 정의하는 데 사용되는 다른 두 가지 중요한 속성이 있습니다 : bounds
와 center
.
bounds
- 설명 :
bounds
는 뷰의 내부 좌표 시스템에서 뷰의 사각형 영역을 정의합니다.bounds
는CGRect
로 정의되며, 원점은 보통(0, 0)
이지만 뷰의 컨텐츠가 스크롤되거나 이동할 수 있는 경우 다른 값이 될 수 있습니다.- 구성 요소 :
- origin : 기본적으로
(0, 0)
이지만, 뷰의 컨텐츠가 스크롤되거나 움직이는 경우에는 달라질 수 있습니다.- size : 뷰의 크기를 나타내며,
frame
의size
와 동일합니다.- 용도 :
bounds
는 뷰의 내부 레이아웃을 정의할 때 사용됩니다. 특히, 뷰가 스크롤되거나 클립핑될 때 유용합니다.
let view = UIView(frame: CGRect(x: 20, y: 20, width: 100, height: 100))
view.backgroundColor = .lightGray
print(view.bounds) // 출력: (0.0, 0.0, 100.0, 100.0)
// bounds의 origin을 (10, 10)으로 변경
view.bounds = CGRect(x: 10, y: 10, width: view.bounds.width, height: view.bounds.height)
print(view.bounds) // 출력: (10.0, 10.0, 100.0, 100.0)
self.view.addSubview(view)
frame
과 달리 bounds
의 origin
은 (0, 0)
이고, size
는 frame
과 동일합니다. 이 bounds
는 뷰의 자체 내부 좌표계를 기준으로 한 사각형을 정의합니다.결국 origin을 변경해도 Frame에 의해 결정되기 때문에 UI가 변경된 부분은 없음
Center
- 설명 :
center
는 뷰의 중심점의 좌표를 나타내며, 부모 뷰의 좌표 시스템을 기준으로 합니다.center
는CGPoint
로 정의되며,frame
의origin
과size
를 기반으로 계산됩니다.- 구성 요소 :
- x : 뷰의 중심점의 x 좌표.
- y : 뷰의 중심점의 y 좌표.
- 용도 : 뷰를 이동하거나 배치할 때
center
를 사용하면, 뷰의 중심을 기준으로 위치를 설정할 수 있어 더 직관적입니다.
let view1 = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
view1.backgroundColor = .lightGray
print(view1.center) // 출력: (50.0, 50.0)
self.view.addSubview(view1)
let view2 = UIView(frame: CGRect(x: 20, y: 20, width: 100, height: 100))
view2.backgroundColor = .lightGray
print(view2.center) // 출력: (70.0, 70.0)
self.view.addSubview(view2)
center
는 frame
의 중심을 나타냄
(width: 100, height: 100)
의 중심 좌표는 (50, 50)
인데
(0, 0)
이므로 (50, 50)
이 나옴(20, 20)
이므로 (70, 70)
이 나옴3-4. 정리
frame, bounds, center의 차이점
- frame : 부모 뷰의 좌표 시스템을 기준으로 뷰의 위치와 크기를 정의합니다.
- bounds : 뷰의 자체 좌표 시스템을 기준으로 뷰의 내부 영역을 정의합니다.
- center : 부모 뷰의 좌표 시스템을 기준으로 뷰의 중심점을 정의합니다.
Auto Layout의 기본적인 아이디어는 View들간의 관계를 통해서 상대적인 위치를 설정하고자 함에 있다.
이 경우 이웃하는 View들에 대해 상대적인 위치를 정하게 되어서 모든 기기들에 대해 View의 위치와 크기를 확정할 수 있다.
물론 이 경우에도 View에 대한 x, y좌표와 너비, 높이가 모두 확정될 수 있어야한다.
💡일부 View의 경우 특성에 따라 자신의 size가 결정되는 View가 있다.
대표적으로 Label은 text의 길이나 폰트 등으로 스스로 크기를 결정한다.
ImageView 또한 별도의 설정이 없는 한, Image의 크기만큼 그 크기를 결정한다.
이를 Intrinsic Content Size라고 한다.
let view = UIView()
view.backgroundColor = .lightGray
view.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(view)
view.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 100).isActive = true
view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 100).isActive = true
view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -80).isActive = true
view.heightAnchor.constraint(equalToConstant: 500).isActive = true
true
- AutoLayout을 따르지 않고 frame을 따르겠다(Frame-Based Layout)false
- AutoLayout을 따르겠다(AutoLayout)addSubview로 추가를 하고 제약조건을 설정해야함
그렇지 않으면 부모 뷰에 추가되지 않았기 때문에 제약조건 설정시 오류 발생
trailingAnchor(오른쪽)
/ BottomAnchor(아래)
에 한해서
-(minus)
를 붙여줘야함
상단에 컨트롤 부분을 건드리고 싶지 않고 SafeArea 부분만 건드리고 싶은 경우
self.view(rootView)
에 붙일 경우 SafeAreaLayoutGuide
를 사용
sodeulButton.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 20).isActive = true
sodeulButton.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor, constant: 20).isActive = true
x축과 y축 모두에 제약사항을 명확히 해야함
View | Intrinsic content Size |
---|---|
UIView와 NSView | 내재적인 콘텐츠 크기가 없습니다. |
Slider | 너비만 정의(iOS) |
슬라이더 유형(OS X)에 따라 너비, 높이 또는 둘 다를 정의 | |
Labels, Buttons, Switches, Text Field | 높이와 너비를 모두 정의합니다. |
Text View, Image Views | 내재적 콘텐츠의 크기는 다양할 수 있습니다. |
왼쪽과 위쪽에 20씩의 간격을 줬을 때…
View
는 Window의 제어 아래
에 있다. Window
는 Constraint Engine과 소통
한다. Engine에 전달
되고 이는 View
에 있는 Variable
을 통해 resolve
된다.Equation
과 Variable
을 통해 Engine이 View
에게 값이 변경되었음을 전달View
는 SuperView
에게 setNeedsLayout()을 호출
하게 된다.(이제 제약조건이 설정되었으니까 그 다음은 Layout을 해야하므로)LayoutSubview에서는 어떤일이 일어날까?
Layout을 만들라고 요청을 받은 View는 다시 Engine으로부터 실질적인 값을 받아와서 subView의 bounds와 center를 설정한다.
많은 내용이 있어서 핵심만 정리하고 아래의 공식문서에서 확인 가능
아래 공식문서 참고.. 너무 어려운 내용..
.isActive = true
일일이 달아주는 부분 없애기let cons = [
lbl1.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
lbl1.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 100),
lbl1.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 300)
]
NSLayoutConstraint.activate(cons)
extension UIView {
func anchor(top: NSLayoutYAxisAnchor?,
right: NSLayoutXAxisAnchor?,
bottom: NSLayoutYAxisAnchor?,
left: NSLayoutXAxisAnchor?,
padding: UIEdgeInsets = .zero,
size: CGSize = .zero) {
translatesAutoresizingMaskIntoConstraints = false
if let top = top {
topAnchor.constraint(equalTo: top, constant: padding.top).isActive = true
}
if let right = right {
rightAnchor.constraint(equalTo: right, constant: -padding.right).isActive = true
}
if let bottom = bottom {
bottomAnchor.constraint(equalTo: bottom, constant: -padding.bottom).isActive = true
}
if let left = left {
leftAnchor.constraint(equalTo: left, constant: padding.left).isActive = true
}
if size.width != 0 {
widthAnchor.constraint(equalToConstant: size.width).isActive = true
}
if size.height != 0 {
heightAnchor.constraint(equalToConstant: size.height).isActive = true
}
}
}
사용
greenView.anchor(top: view.safeAreaLayoutGuide.topAnchor,
right: view.safeAreaLayoutGuide.rightAnchor,
bottom: nil,
left: nil,
padding: .init(top: 0, left: 0, bottom: 0, right: 30),
size: .init(width: 100, height: 200)
)
redView.anchor(top: greenView.bottomAnchor,
right: greenView.rightAnchor,
bottom: nil,
left: nil,
padding: .init(top: 15, left: 0, bottom: 0, right: 0),
size: .init(width: 100, height: 100)
)
드래그한 ()부분을 아래 홈페이지에 넣어보기
https://www.wtfautolayout.com/
https://ios-development.tistory.com/90
iOS에서 View는 어떻게 그려질까?
뷰 좌표 시스템과 UIView의 frame 구조
iOS) Auto Layout 정복하기 (5/5) - 코드로 Auto Layout 설정하기
제약조건 공식 문서
Constraint 공식 문서
https://ios-development.tistory.com/67
https://ios-development.tistory.com/90