article thumbnail image
Published 2023. 2. 2. 02:57

view를 화면에 배치하기 위해서는 다음과 같은 정보가 필요하다.

  • size (width, height)
  • location (x, y)

Frame-based Layoutframe을 통해 이러한 정보(size, location)들을 value로 직접 지정한다.
이는 원하는 위치에 설정하면 되기에 유연하며 성능이 빠르다는 장점이 있었지만,
Externel changes(외부 변화) 혹은 Internal changes(내부 변화)가 발생하면 개발자가 직접 관리해주어야 한다.

External changes란 superview의 size나 shape이 변경될 때 발생한다. 디바이스 회전, 다른 크기의 스크린 지원과 같은 예시가 있다.
Internal changes란 UI변경으로 인해 view의 size가 변경될 때 발생한다. 뉴스앱에서 뉴스의 길이로 인해 view의 길이가 변하는 경우가 있다.

이를 극복하기 위해 Autoresizing mask 를 도입함에도 불구하고, 상당 부분 유저가 직접 관리해야 했다.

반면 Auto Layout은 View에 정의된 "constraints"들에 따라 sizelocation 동적으로 계산하여 배치하며,
Externel changes(외부 변화) 혹은 Internal changes(내부 변화)에 대해 동적으로 반응하도록 UI를 구성할 수 있다.

Constraints

Auto Layout에선 view를 배치하기 위해 필요한 size, location 정보들이 일련의 constraints로 정의가 된다.
각 constraintsview 간의 관계를 나타내며, linear Equation으로 정의된다.

Auto Layout은 이러한 constraints들을 기반으로 동적으로 view의 size와 location 계산한다.
이때 계산에 의해 도출되는 solution은 하나여야 한다.

만약 필요한 constraints가 부족하다면 Unsatisfiable Layouts 에러가 나게 되고,
solution이 2개 이상이라면, Ambiguous Layouts 에러가 나게 된다.

Attributes

constraints에는 위와 같이 "어떤 view의 어떤 attribute에 관계를 정의"하게 되는데, attribute에는 여러 종류가 있다.

Attributes Description 주의 사항
Height, Width view의 size를 정의한다. - 다른 view와의 관계가 아닌, 직접 constantvalue로 설정할 수 있다.
- 관계로 정의할때는, Width, Height Attributes와 결합이 가능하다.
Top, Bottom 말 그대로 위아래를 의미한다.
아래로 갈수록 값이 커진다.
- 관계로만 정의가 가능하며, Center Y, Top,Bottom, Baseline attributes와 결합이 가능하다.
Left, Right 말 그대로 좌, 우를 의미한다.
오른쪽으로 갈수록 값이 커진다.
- 관계로만 정의가 가능하며, Left, Right, CenterX attributes와 결합이 가능하다.
Leading, Trailing trailing으로 갈수록 값이 커진다.
left-to-right layout direction의 경우, 오른쪽으로갈수록 값이 커진다.
right-to-left layout direction의 경우, 왼쪽으로 갈수록 값이 커진다.

- 관계로만 정의가 가능하며, Leading, Trailing, CenterXattributes와 결합이 가능하다.
CenterX, CenterY CenterX는 view frame의 Horizontal center
CenterY는 view frame의 Vertical center
- 관계로만 정의가 가능하며, CenterX는 Leading, Trailing, Left, Right와 결합이 가능하다.
- CenterY는 Top, Buttom, Baseline과 결합이 가능하다.
firstBaseline, lastBaseline 여러 줄의 text가 있을 경우,
text의 top row, bottom row를 의미한다.
- 관계로만 정의가 가능하며, Center Y, Top,Bottom, Baseline attributes와 결합이 가능하다.

Leading, Trailing의 경우에는 우리나라와 같이
글을 왼쪽에서 오른쪽으로 읽는 언어에서는 Leading은 Left, Trailing은 Right가 된다.
하지만, 아랍권과 같이 글을 오른쪽에서 왼쪽으로 읽는 국가에서는 Leading은 Right가 되고, Trailing이 Left가 된다.

즉, Leading은 텍스트의 시작점, Trailing은 텍스트의 끝점이다.

Apple 공식문서에서는 특정하게 왼쪽 오른쪽에 레이아웃을 고정해야만 하는 경우를 제외하곤,
Leading, Trailing의 사용을 권장한다.

이렇게 constraints는 다른 view와의 attribute간의 관계를 정의하여 표현하지만,
size attributes (width, height)의 경우는 상수로 설정이 가능하다.

someButton.widthAnchor.constraint(equalToConstant: 100)


Priorities

Priorities는 말그대로 "constraints에 부여하는 우선순위" 이며, 1~1000까지 설정이 가능하다.
1~2 단위로 섬세하게 조절할 수도 있지만,
통상적으로 low(250), medium(500), high(750), required(1000)으로 사용한다.

의미에서도 알 수 있듯이, 우선순위가 높은 constraints가 먼저 적용되며,
default로는 required(1000)로 적용이 된다.

Intrinsic Content Size

UILabel, UIButton, UITextField 등 내부에 text와 같은 고유 content가 존재하는 view들이 있다.
view를 정의하기 위해선 position과 size가 필요하지만,
고유 content가 있는 view들은 content에 의해 natural한 size를 가지게 된다.
natural한 size를 그들의 Intrinsic Content Size라 부른다.

view Intrinsic content Width Intrinsic content Height
UIView X X
UISliders O X
UILabel, UIButton, UISwitch, UITextField O O
TextView, ImageView 상황에 따라 다름.

TextView같은 경우는 scroll을 disable하게 되면, Intrinsic Content Size가 줄 바꿈 없이 text 크기를 기준으로 결정된다.
scroll을 enable하게 되면, Intrinsic Content Size를 지원하지 않는다.

ImageView같은 경우는 image가 없을 경우 Intrinsic Content Size를 지원하지 않지만,
이미지가 생기게 되면, 이미지 사이즈에 따라 Intrinsic Content Size가 결정된다.

CHCR (Content Hugging & Compression Resistance)

Intrinsic Content Size를 유지하기 위해
각 dimension(가로, 세로)마다 Content Hugging과 Compression Resistance(CHCR) constraints이 사용된다.

Content Huggingview를 content주변으로 당기는 반면,
Compression Resistancecontent가 잘리지 않도록 view를 밖으로 밀어낸다.

Default로는 Content Hugging은 250의 Priority를
Compression Resistance는 750의 Priority를 가지게 된다.
따라서, 개발자가 size에 대한 constraints를 추가하게 되면, Intrinsic Content Size는 무시되게 된다.
이는 이전에도 언급했듯이 직접 추가한 constraints는 default로 1000의 Priority를 가지기 때문이다.

아래와 같이 2개의 Intrinsic Content Size를 가지는 Label이 두 개가 있다고 하자.
(size에 대한 constraints는 추가하지 않았다.)

ALabel.trailing = 1.0 X BLabel.trailing + 20

위와 같은 constraints를 추가하게 되면 에러가가 나게 되는데, 이는 둘 중 하나는 intrinsic Content Width를 유지 할 수 없게 되기 때문이다.
따라서 해당 상황에서는 CHCR의 Priority를 조절해주어야 한다.

만약 ALabel에 Horizontal Content Hugging priority를 높여주게 되면
ALabelview를 content주변으로 당기는 힘이
BLabel보다 더 강하기 때문에, BLabel은 늘어나게 된다.
더 정확히는, ALabel Intrinsic Content Width를 유지하게 되고
BLabel Intrinsic Content Width를 유지하지 못하게 된다.

하지만 해당 상태에서도 ALabel이나 BLabel의 content의 사이즈가 너무 커지게 되면 에러가 나게 된다.
이는 둘의 Compression Resistance Priority, 즉 서로 content가 잘리지 않기 위해 view를 미는 힘이 같기 때문이다.


만약 ALabel의 Compression Resistance Priority를 높여주게 되면,
ALabel의 잘리지 않기 위해 미는힘이 더 강해지기 때문에,
BLabel의 content가 잘리게 된다.


Stack View

StackView란, view들을 "vertical "혹은 "horizontal"하게 배치하기에 유용한 인터페이스 이다.

아래와 같이 3개의 UIButton일 같은 사이즈로 일렬로 정렬한다고 가정해 보자.

이들을 배치하기 위해 수많은 constraints가 필요하게 된다.

하지만, StackView는 UI를 addArrangedSubViews(_:)를 통해 추가하게 되면,
Alignment, Spacing, Axis, Distribution 설정만 해주면, 내부의 view들을 알아서 Auto Layout를 잡아준다.

Position & Size

StackView의 경우에는 기본적으로 view이기 때문에, position은 필수적으로 지정해주어야 한다.

size의 경우에는 내부 view들의 size에 기반하여 계산이 된다.
axis방향은 내부 view와 spacing의 합으로 계산이 되고,
axis의 수직인 방향해당 축으로 가장 사이즈가 큰 view의 size로 결정이 난다.

하지만, 내부 view의 size를 가지고 있지 않다면, stackView의 size를 지정해주어야 한다.

Spacing

StackView 내부 view 간의 간격을 설정할 수 있다.

추가적으로 isLayoutMarginRelativeArrangement 프로퍼티를 true로 설정해 주게 되면,
spacing 외에도 stackView와 view의 Edge 사이에 margin을 줄 수 있다.

stackView.isLayoutMarginsRelativeArrangement = true
stackView.layoutMargins.left = 10.0
//stackView.layoutMargins.right = 10.0

Axis

StackView 내부 View들이 배치되는 방향을 결정한다.
vertical로 설정하게 되면, 수직으로 view들이 배치되고,
horizontal로 설정하면, 수평으로 view들이 배치된다.

addArrangedSubViews(_:)를 통해 추가된 view들은
arrangedSubviews라는 배열 프로퍼티를 통해 관리되는데,
이 배열의 순서대로 axis에 따라 배치된다.

func addArrangedSubview(_ view: UIView)

var arrangedSubviews: [UIView] { get }

Alignment

Alignment는 arrangedSubviews들에 대해 axis에 수직방향의 layout을 결정한다.


다음과 같이 중앙의 UIButton만 height를 다르게 설정했다고 가정하자.

fill

fill의 경우에는 axis의 수직인 방향으로 view들의 크기를 StackView의 크기만큼 resize 한다.
stackView의 aixs의 수직인 방향의 크기는 해당 방향으로 제일 큰 view의 크기와 같기 때문에,
아래에선 ButtonB의 Height와 같아지게 된다.

center

view들을 stackView의 중심으로 정렬한다.

top & bottom , leading & trailing

horizontal Stack View의 경우에는 top, bottom을 사용하며,
top 혹은 bottom 쪽으로 정렬한다.

vertical Stack View의 경우에는 leading, trailing을 사용하며,
leading 혹은 trailing 쪽으로 정렬한다.

추가적으로 horizontal stack View의 경우 lastBaseline, firstBaseline을 사용할 수 있다.

Distribution

Alignment가 axis에 수직인 방향으로 view들을 어떻게 분배할지를 결정했다면,
Distribution axis 방향으로 view들을 어떻게 분배할지를 결정한다.

fill

axis를 따라 사용 가능한 공간을 채우도록 view의 사이즈를 조절하여 정렬한다.

만약, arrangedSubviews들이 stackView보다 크다면,
compression resistance priority가 가장 낮은 view 하나를 선택하여, 해당 view는 content가 잘리게 된다.
반면, stackView보다 작다면,
content hugging priority가 가장 낮은 view 하나가 Intrisinc Content Size를 유지하지 못하고 늘어나게 된다.

중요한 점은 view하나만을 선택하여 조절한다는 것이다.

만약 우선순위가 정해져 있으면, arrangedSubviews 중 가장 첫 번째 element를 조절하게 된다.

fillEqually

fillEqually는 fill과 유사하게 axis 축으로 사용가능한 모든 공간을 채우도록 view의 사이즈를 조절한다.
하지만 모든 view가 동일한 사이즈를 갖는다.

fillProportionally

fillProportionally도 axis축으로 사용가능한 모든 공간을 채우도록 view의 사이즈를 조절한다.
view의 size 조절은 축과 같은 방향의 intrinsic content size에 비례하여 공간을 채우게 된다.

equalSpacing

axis축으로 사용가능한 모든 공간을 채우지만, view의 Size는 유지하고 빈 공간을 추가한다.
이 빈 공간은 spacing이 아닌, UILayoutGuide라는 빈 상자를 통해 채워지는데,
view사이의 여백이 동일해지도록 조건이 설정된다.

이 역시 view들의 size가 크게 되면 Compression Resistance Priority가 낮은 view의 content가 잘리면서 줄어들게 된다.

equalCentering

EqualCentering은 equalSpacing과 같이 사용가능한 모든 공간을 UILayoutGuide를 통해 채우게 된다.
다른 점은, 각 view 간의 중심축 간 간격이 같도록 빈 공간을 추가한다.

arrangedSubviews들이 stack view보다 클 경우, spacing 이 줄어들게 된다.
최소한으로 spacing을 줄여도 큰 경우는 Compression Resistance Priority가 낮은 view의 content가 잘리면서 줄어들게 된다.

Reference

Apple Documentation

UIStackView and Auto Layout

복사했습니다!