오토레이아웃을 쉽고 빠르게, 스택 뷰(Stack View)

Eddy·2022년 2월 14일
13
post-thumbnail

오토레이아웃을 쉽게 잡는 꿀팁

여태까지 직접 조건을 설정하는 방법과 고유 콘텐츠 사이즈 조건을 알아봤다.

하지만 여기서 끝이 아니다.

오토레이아웃을 잡을 때 중요한 원칙이 하나 있다.

레이아웃을 잡을 때는, 무조건 Stack View부터 쓴다.
Stack View로 해결할 수 없을 때만, 직접 조건(Constraints)을 설정한다.

아니, Stack View가 도대체 뭐길래
우리가 여태까지 배운 조건을 최대한 쓰지 말라고 하는 걸까? 🤔

Stack View의 등장

Stack View는 오토레이아웃을 편리하게 사용하도록 도와주기 위해 나온 친구다.
2015년 iOS 9에서 처음으로 등장했다.

Stack View는 그 자체로 내용을 가지진 않고
UI 요소를 가로 혹은 세로로 묶어주는 역할을 한다.

세로 방향으로 묶어주는 Stack View가 Vertical Stack View고,
가로 방향으로 묶어주는 Stack View가 Horizontal Stack View다.

Stack View로 UI를 묶은 다음, 어떻게 정렬/배치할 건지 정해준다.
그러면 Stack View 안에서 자동으로 레이아웃이 잡힌다.

PPT를 좀 만들어봤다면, '맞춤' 혹은 '정렬' 기능을 써봤을 것이다.
여러가지 도형과 텍스트 상자의 위치를 예쁘게 잡으려면, 필수 기능이다.

Stack View는 이런 PPT의 '맞춤' '정렬' 기능을
iOS UI에 자동으로 적용하는 빈 박스라고 생각하면 편하다.

쉽고, 가볍고, 바꾸기 쉬운 Stack View

쉽다

복잡한 화면을 오토레이아웃으로 모두 설정해주려면
엄청나게 많은 조건들이 필요하고 계산이 복잡해진다.

하지만 UI를 Stack View 안에 추가하고,
Distribution, Alignment, Spacing 설정만 딱 잡아주면?

개발자가 직접 설정해줘야할 조건(Constraints)이 엄청 줄어든다.

주어진 설정값에 맞게 Stack View가 자동으로 조건을 조정해주기 때문이다.

빠르다

Stack View 자체는 보여줄 컨텐츠가 없다.

따라서 iOS가 화면을 띄울 때 별도로 렌더링(Rendering)하는 수고가 들지 않는다.
일반 뷰보다 훨씬 가볍고 빠르게 그려진다.

뷰를 추가하거나 삭제할 때 편리하다.

이미 수많은 조건으로 오토레이아웃을 잡아놓은 이런 화면이 있다.


(출처: Mysteries of Auto Layout, Part 1)

꽤 복잡한 화면이다.

그런데 이때 앱 카테고리와 별점 리뷰 사이에 새로운 뷰를 끼워넣어야 하는 상황이 생겼다.
어떻게 해야할까? 의도한 레이아웃을 유지하면서 새로운 뷰를 추가할 수 있을까?

정답은 '할 수 있지만, 매우 귀찮다'이다.

새로운 뷰의 조건을 하나하나 설정해줘야할 뿐만 아니라,
이미 있던 조건들도 모두 맞춰서 바꿔줘야한다.

심지어는 원래 조건을 다 지우고 새로운 조건을 추가해줘야할 수도 있다.

아... 너무 귀찮다.... 😰

이런 상황에 Stack View가 진가를 발휘한다.

Stack View는 View를 추가하기만 하면,
알아서 설정에 맞게 View의 레이아웃을 잡아준다!

(View를 삭제할 때도 마찬가지)

덕분에 개발자는 조건을 일일이 수정할 필요가 없다.

디자인 시점이든, 실행 시점이든,
그냥 간단하게 Stack View에 새로운 View를 추가하거나 있던 View를 삭제하기만 하면 된다.

중첩하면 복잡한 레이아웃도 구현 가능

Stack View 하나로 복잡한 UI 레이아웃을 다 잡을 수 있을까?

Stack View 하나만 놓고 보면 다소 간단해보인다.
하지만 Stack View 안에 Stack View를 넣을 수 있다.

이렇게 Stack View를 중첩시키면
가로 세로 스택 뷰를 쌓아서
복잡한 레이아웃도 얼마든지 만들어낼 수 있다.


(출처: Apple)

물론 Stack View 만으로는 구현할 수 없는 레이아웃도 있겠지만,
기본은 Stack View로 잡은 다음 필요한 조건만 추가로 설정해주면 된다.

이렇게 Stack View의 장점을 알아봤으니,
이제 이 말이 이해되기 시작한다.

레이아웃을 잡을 때는, 무조건 Stack View부터 쓴다.
Stack View로 해결할 수 없을 때만, 직접 조건(Constraints)을 설정한다.

Stack View의 설정

Stack View의 핵심 프로퍼티는 4가지가 있다.

하나는 가로 스택 뷰인지, 세로 스택 뷰인지를 결정하는 Axis다.
이건 간단하니까 설명은 필요없을 것 같다.

그 외에 하위 뷰 사이 여백을 결정하는 Spacing,
하위 뷰의 크기 배분을 결정하는 Distribution,
하위 뷰의 위치 정렬을 결정하는 Alignment이 있다.

이 프로퍼티를 어떤 값으로 설정하느냐에 따라서,
Stack View 안에 있는 하위 뷰의 레이아웃이 자동으로 바뀌게 된다.

각각의 설정값을 알아보고, 각 설정값이 실제로 어떤 조건들을 만들어내는지까지 파보도록 하자.
이 파트만 잘 이해한다면, Stack View를 자유자재로 사용할 수 있게 될 테니 꼼꼼히 읽어보도록 하자.

사이 여백: Spacing

Stack View는 spacing에 설정된 값만큼, 하위 뷰들 사이에 여백을 준다.

예를 들어 spacing을 8로 설정했다면, 하위 뷰는 모두 8포인트씩 동일한 간격을 두고 배치된다.

이건 쉽군 😎

크기 배분: Distribution

여기서부터가 중요하다.
Distribution은 하위 뷰의 크기를 결정하는 설정값이다.

Vertical Stack View를 기준으로 설명한다.
Horizontal Stack View도 축의 방향만 바뀌면 똑같다.

Fill

Fill은 Stack View의 세로 공간을 모두 채울 수 있도록 하위 뷰를 늘린다.

먼저, Stack View의 크기가 정해져있어야 한다.

Stack View의 높이가, 하위 뷰 높이를 합친 것보다 크면 공간이 남을 것이다.
이 때 Fill은 하위 뷰 하나를 정해서, 남는 공간에 맞게 늘려버린다.

반대로 Stack View의 높이가, 하위 뷰 높이를 합친 것보다 크면 공간이 부족하다.
이 때 Fill은 하위 뷰 하나를 정해서, 부족한 공간만큼 축소시킨다.

그럼 늘리거나 줄일 그 하나는 어떻게 정할까?

  • 하위 뷰를 늘려야 하는 경우, '허깅 우선순위'가 낮은 뷰부터 늘린다.
  • 하위 뷰를 줄여야 하는 경우, '컴프레션 저항 우선순위'가 낮은 뷰부터 줄인다.

허깅 & 컴프레션 우선순위가 같다면?

우리가 처음 하위 뷰를 Stack View에 넣으면
허깅 & 컴프레션 우선순위는 모두 디폴트값으로 되어있다.

이렇게 우선순위가 같은 경우, Fill은 에러를 뱉는 게 아니라
하위 뷰의 index를 기준으로 마지막에 있는 뷰를 늘리거나 줄인다.

공평한 거 필요 없고,
그냥 '한 녀석만 늘려서 맞춘다'는 게 Fill의 포인트다.

Fill이 설정하는 조건

  • Subview1.top을 Stackview.top에 고정시킨다.
  • Subview3.bottom을 Stackview.bottom에 고정시킨다.
  • Subview 사이에 Spacing을 추가한다.

Fill Equally

Fill Equally는 Stack view 공간을 n분의 1로 똑같이 나눠서 채운다.

Fill Equqlly는 Fill과 비슷하지만,
Fill은 한 녀석만 늘려서/줄여서 공간을 채우는 반면
Fill Equqlly 모든 뷰의 높이를 똑같이 만들어서 채운다.

같이 먹은 밥값 계산하는 상황에 비유해보자.

각자 주머니에 있는 돈 다 꺼낸 다음,
부족한 돈이 있으면 가위바위보해서 진 한 명이 채우는 게 Fill이다.

Fill Equally는 그런거 필요없고
처음부터 n으로 나눈 다음 더치페이한다.

Fill Equally가 설정하는 조건

  • Subview1.top을 Stackview.top에 고정시킨다.
  • Subview3.bottom을 Stackview.bottom에 고정시킨다.
  • Subview 사이에 Spacing을 추가한다.
  • Subview1, 2, 3의 Height를 모두 같게 만든다.

Fill Proportionally

Fill Proportionally는 Stack view 공간을 각 하위 뷰의 '고유 콘텐츠 사이즈에 비례하게' 나눠서 채운다.

Fill Proportionally는 남거나 부족한 공간을 동등하게 나누지 않고, 각 하위 뷰가 가진 '고유 콘텐츠 사이즈'에 비례하게 나눈다.
원래 높이가 큰 뷰는 그만큼 더 많이 늘어나고, 높이가 작은 뷰는 조금 늘어나게 된다.

같이 먹은 밥값 계산하는 상황으로 다시 가보자.

Fill은 각자 주머니에 있는 돈 다 꺼낸 다음,
부족한 돈이 있으면 가위바위보해서 진 한 명이 채운다.

Fill Equally는 처음부터 n으로 나눈 다음 더치페이한다.

Fill Proportionally는 각자 주머니에 있는 돈을 다 꺼내게 한다.
그리고 각자 가진 돈의 비율을 구한다. 그 비율에 따라 돈을 더 낸다.

A는 3만원, B는 5만원, C는 2만원이 있었다고 하자.
꺼낸 돈의 총합에 대한 각자의 비율은 A가 30%, B가 50%, C가 20%이다.

그러면 A는 밥값의 30%를 내고, B는 밥값의 50%를 내고, C는 밥값의 20%를 낸다.
'평소에 부자인 녀석이 좀 더 내자'는 방법이다.

Fill Proportionally 설정하는 조건

  • Subview1.top을 Stackview.top에 고정시킨다.
  • Subview3.bottom을 Stackview.bottom에 고정시킨다.
  • Subview 사이에 Spacing을 추가한다.
  • StackView.Height에 각 하위 뷰의 ratio를 곱해서 subview.height를 지정한다.

Equal Spacing

Equal Spacing은 Stack view 공간을 뷰의 크기만큼만 채운다. 뷰의 크기는 그대로 두고, 남는 공간을 동일한 크기의 여백으로 채운다.

Equal Spacing에서는 하위 뷰의 크기가 늘어나지 않는다.
대신 남는 공간은 빈 여백으로 채우게 되는데, 이때 빈 여백은 Spacing과는 다르다.
LayoutGuide라고 하는 빈 상자를 사용해서 채운다.

택배 상자에 물건 담고 남는 공간에 뽁뽁이 채우는 거랑 비슷하다.

뷰 사이사이에 LayoutGuide 있고, 각 뷰 사이의 여백이 동일해지도록 조건이 설정된다.

만약 하위 뷰의 크기가 줄어들어야 하는 상황이면,
컴프레션 저항 우선순위가 낮은 뷰부터 줄인다.
우선순위가 충돌하면 마지막 뷰부터 줄인다.

Fill Equally가 설정하는 조건

  • Subview1.top을 Stackview.top에 고정시킨다.
  • Subview3.bottom을 Stackview.bottom에 고정시킨다.
  • Subview 사이에 Spacing을 추가한다.
  • 남는 공간을 layoutGuide.height가 똑같이 나누도록 설정한다.

Equal Centering

Equal Centering은 Stack view 공간을 뷰의 크기만큼만 채운다. 각 뷰의 중심축 사이 간격이 똑같도록 빈 여백으로 채운다.

Equal Centering도 하위 뷰의 크기가 늘어나지 않고 남는 공간은 LayoutGuide 로 채운다.

이 때 LayoutGuide 의 크기를 설정하는 부분이 다른데,
각 뷰의 중심축 간 간격이 똑같도록 만든다.

즉, view1 높이의 절반 + spacing + view2 높이의 절반이 똑같다는 뜻이다.

view1과 view2의 높이에 따라서 spacing은 동적으로 달라진다는 걸 알 수 있다.
더 큰 뷰 사이의 공간은 좁고, 더 작은 뷰 사이 공간은 넓게 설정될 것이다.

만약 하위 뷰의 크기가 줄어들어야 하는 상황이면, 컴프레션 저항 우선순위가 낮은 뷰부터 줄인다.
우선순위가 충돌하면 마지막 뷰부터 줄인다.

Fill Equally가 설정하는 조건

  • Subview1.top을 Stackview.top에 고정시킨다.
  • Subview3.bottom을 Stackview.bottom에 고정시킨다.
  • layoutGuide1.top을 Subview1.centerX에 고정시킨다.
  • layoutGuide1.bottom을 Subview2.centerX에 고정시킨다.
  • layoutGuide2.top을 Subview2.centerX에 고정시킨다.
  • layoutGuide2.bottom을 Subview3.centerX에 고정시킨다.
  • layoutGuide.height가 모두 같아지도록 설정한다.

위치 정렬: Alignment

Alignment는 더 직관적이라서 이해하기 쉽다.
하위 뷰를 어느쪽으로 정렬시킬지 결정한다.

조금 더 정확히 말하면,
Axis와 평행한 축의 조건을 Distribution이 결정했다면,
Axis와 직각인 축의 조건은 Alignment가 결정한다.

Vertical Stack View의 경우
'높이'와 관련된 조건을 Distribution이 결정하고
'너비'와 관련된 조건을 Alignment이 결정한다.

이번에도 Vertical Stack View를 기준으로 설명한다.
Horizontal Stack View도 축의 방향만 바뀌면 똑같다.

Fill

너비 양쪽 끝을 늘려서 꽉 채운다.

Leading (Top)

하위 뷰의 너비를 그대로 두고 왼쪽으로 정렬 시킨다.
(Horizontal의 위쪽 정렬과 동일하다.)

Trailing (Bottom)

하위 뷰의 너비를 그대로 두고 오른쪽으로 정렬시킨다.
(Horizontal의 아래쪽 정렬과 동일하다.)

Center

하위 뷰의 너비를 그대로 두고 가운데로 정렬시킨다.

요약 정리

  • Stack View를 사용하면 직접 조건을 설정하는 것보다 훨씬 쉽고 빠르게 레이아웃을 잡을 수 있다.
  • 레이아웃을 잡을 때는, 무조건 Stack View부터 쓴다. Stack View로 해결할 수 없을 때만, 직접 조건(Constraints)을 설정한다.
  • Stack View는 설정값에 따라 조건들을 자동으로 생성/변경한다.
  • 하위 뷰 사이 여백을 결정하는 Spacing, 하위 뷰의 크기 배분을 결정하는 Distribution, 하위 뷰의 위치 정렬을 결정하는 Alignment이 있다.
  • 각 설정시 만들어지는 조건들을 잘 알아두면, 오토레이아웃 에러를 해결하고 의도한 레이아웃을 잡는 데 큰 도움이 된다.

🔗 UIStackView and Auto Layout

profile
개발 지식을 쉽고 재미있게 설명해보자. ▶️ www.youtube.com/@simple-eddy

2개의 댓글

comment-user-thumbnail
2022년 7월 21일

덕분에 쉽게 이해할 수 있었습니다 감사합니다 :)

답글 달기
comment-user-thumbnail
2023년 2월 5일

fill proportionally == 평소에 부자인 녀석이 좀 더 내자 ..
무릎을 탁 치고 갑니다

답글 달기