오토레이아웃이 자동 설정하는 조건, 고유 콘텐츠 크기(Intrinsic Content Size)

eddy_song·2022년 2월 13일
4
post-thumbnail

지난 글에서 봤듯이, 일반 뷰(UIView)와 레이블(UILabel)의 오토레이아웃이 달랐다.
레이블은 조건을 다 지정해주지 않아도 에러가 뜨지 않는다.

왜 그럴까? 🤔

콘텐츠가 있는 뷰와 없는 뷰

iOS의 UIKit에는 여러가지 UI 요소들이 있다.

UI 요소들 중에서도 UIView, UIStackVIew, UITextView 같은 UI들은 조금 특이한 친구들이다.
아직 내용이 들어있지 않은 빈 박스다. 미리 콘텐츠가 정해져있지 않다. (HTML의 <div>, <section> 같은 역할)

반면, 대부분의 UI는 안에 '콘텐츠'가 포함되어있다.
(UILabel, UIButton, UISwitch, UISegementedControl 등)

안에 띄울 텍스트, 아이콘의 크기 등 안에 담긴 기본 콘텐츠가 있다.

이 콘텐츠의 크기를, '고유 콘텐츠 사이즈(Intrinsic Content Size)'라고 한다.

이렇게 고유 콘텐츠가 포함된 UI들은 사이즈에 맞춰서 조건이 자동으로 만들어진다.

안에 들어가 있는 고유 콘텐츠(폰트 크기, 텍스트의 양, 아이콘, 이미지 등)의 크기에 맞게 조건을 설정한다.
개발자가 따로 크기를 지정해주지 않아도 된다.

그래서 '레이블'의 경우 조건을 다 지정해주지 않아도 에러가 뜨지 않았던 것!

방금 전에 우리가 만들었던 레이블에는 이미 'AUTO LAYOUT'이라는 텍스트가 들어있다.
텍스트에 맞게 크기 조건이 설정된다. 결과적으로 위치/크기를 모두 계산할 수 있다.

어떤 뷰는 콘텐츠가 있고, 어떤 뷰는 콘텐츠가 없는 걸까?

이건 그냥 알아두는 수밖에 없다.

대부분의 UI가 콘텐츠를 포함하기 때문에,
고유 콘텐츠 사이즈가 없는 뷰만 기억해놓으면 쉽다.

  • UIView, UIStackView: 아예 없음.
  • UITextview: 보통은 있고, 스크롤 가능하게 설정하면 없음.
  • UIImageview: 이미지가 로드되면 있음.
  • UISlider: '세로(Y축)' 사이즈만 없음.
  • 그 외: 있음.

콘텐츠에 따라 늘었다 줄었다 하는 뷰

아이폰의 메시지 앱을 한번 보자.
사용자가 입력한 텍스트의 양에 따라서 말풍선 모양 뷰의 크기가 달라진다.

이 때 개발자가 직접 콘텐츠 크기를 계산해서 조건으로 설정해주지 않아도 된다.
말풍선 안의 내용이 많으면, 많은만큼 세로 길이가 늘어난다.

이게 바로 고유 콘텐츠 사이즈가 있는 이유다.

실행 시점에 콘텐츠는 사용자의 입력이나 데이터에 따라서 계속 바뀔 수 있는데,
뷰가 콘텐츠 크기에 맞게 알아서 알맞은 크기로 조절되도록 만들 수 있다.

고유 콘텐츠 사이즈의 우선순위

그렇다면 고유 사이즈가 있는데,
고유 사이즈와 다른 충돌하는 크기 조건을 따로 지정해준다면 어떻게 될까?

자, 빈 스크린에 레이블을 추가했다.

이 상태에서 너비(width) 조건(Constraints)을 콘텐츠 사이즈보다 크게 설정해본다.

고유 콘텐츠 사이즈가 아닌,
새로 설정한 너비 조건으로 레이아웃이 잡힌다.

이번에는 콘텐츠 사이즈보다 너비 조건을 작게 설정해보자.

이번에도 고유 콘텐츠 사이즈는 무시되고,
설정한 너비 조건에 의해서 콘텐츠가 잘려서 보이게 된다.

이렇게 되는 이유는
우리가 저번 글에서 이미 배운 '우선순위' 때문이다.

고유 콘텐츠 사이즈 조건은 따로 인터페이스에 표시가 되지는 않는다.

하지만 사실 별 거 없다.
엔진이 자동으로 추가하는 또 하나의 '조건(Constraints)'일 뿐이다.

엔진은 콘텐츠 사이즈가 바뀔 때마다 사이즈를 계산해서,
다음과 같은 부등식 조건을 자동으로 설정해준다.

View.size >= intrinsicContentSize 

View.size <= intrinsicContentSize 

콘텐츠 사이즈 조건 우선순위의 디폴트 값

오토레이아웃 엔진은 콘텐츠 사이즈 조건 우선순위를 250 혹은 750으로 설정하도록 돼있다.
그런데 우리가 설정하는 조건은 우선순위 값이 디폴트 1000으로 설정된다.

따라서 위의 결과처럼, 콘텐츠 사이즈 조건은 무시되고
직접 설정한 조건에 따라서 레이아웃이 잡힌다.

만약 너비 조건의 우선순위를 1로 낮춰버린다면?
콘텐츠 사이즈 조건에 맞춰서 다시 레이아웃을 계산한다.

콘텐츠 허깅과 컴프레션 저항

콘텐츠 사이즈 조건을 다시 한번 보자.
높이, 너비에 대해 각각 2개씩 총 4개의 부등식으로 되어있다.

// Compression Resistance
View.height >= IntrinsicHeight
View.width >= IntrinsicWidth
 
// Content Hugging
View.height <= IntrinsicHeight
View.width <= IntrinsicWidth

위의 것을 콘텐츠 허깅(Content Hugging) 조건,
밑의 것을 컴프레션 저항(Compression Resistance) 조건이라고 한다.

말이 좀 어려운데, 쉽게 바꿔 말해보자.

허깅
고유 사이즈 이상으로 '늘어나지 않으려고 하는' 조건.
(줄어드는 것은 아님)

컴프레션
고유 사이즈 이하로 '줄어들지 않으려고 하는' 조건.
(늘어나는 것은 아님)

허깅과 컴프레션 조건은, 뷰의 사이즈가 담고 있는 콘텐츠보다
크지도 않고(허깅), 작지도 않게(컴프레션) 맞춰주는 조건이라고 생각하면 된다.

어차피 이 조건은 알아서 생성되니까 알고만 있으면 된다.
더 중요한 건 이 조건의 '우선순위'를 다룰 줄 알아야 한다.

인터페이스 빌더에서 뷰의 속성을 보면,
컨텐츠 허깅과 컴프레션 저항의 우선순위를 정하는 탭이 있다.

허깅의 경우 디폴트 값은 250
컴프레션의 경우, 디폴트 값은 750으로 되어있다.

(만약 A뷰의 컴프레션 조건과 B뷰의 허깅 조건이 충돌하면, 컴프레션 조건이 우선시된다는 의미이다. 바꿔 말하면 컨텐츠가 잘리는 것보다는 뷰가 늘어나는 게 낫다는 뜻.)

개발자가 설정하는 조건의 디폴트 우선순위: 1000
컴프레션 조건의 디폴트 우선순위: 750
허깅 조건의 디폴트 우선순위: 250

허깅 우선순위 정하기

허깅 우선순위와 컴프레션 저항 우선순위는 뷰를 설정할 때 굉장히 많이 다루게 된다.

예를 들어, 텍스트 입력창과 버튼을 가로로 배치한다고 해보자.
각각 양쪽 끝에 위치를 지정해주었고, 둘의 간격은 8포인트로 설정했다.

이상하다. 각자 콘텐츠 사이즈가 있는데... X축에서 에러가 왜 뜨지?

에러가 뜨는 이유는 양쪽 뷰의 '컨텐츠 허깅 조건'이 서로 충돌하고 있기 때문이다.

현재 개발자가 설정해준 조건은 다음과 같다. (X축만)

조건1: 입력창은 스크린 왼쪽 가장자리에서 10포인트 옆이야.
조건2: 버튼은 스크린 오른쪽 가장자리에서 10포인트 옆이야.
조건3: 입력창과 버튼의 간격은 8포인트야.

하지만 실제 오토레이아웃에 적용되는 조건은 그 뿐만이 아니다.

조건4 (허깅): 입력창은 'ID를 입력해주세요' 길이 이상으로 늘어나면 안 돼.
조건5 (허깅): 버튼은 'Button' 길이 이상으로 늘어나면 안 돼.

이렇게 자동으로 콘텐츠 사이즈 조건이 추가된다.

허깅 조건은 250이니까,
현재 우리가 직접 설정해준 조건(1000)이 우선순위가 더 높다.

근데 문제는 설정해준 조건을 만족시키려면,
입력창 혹은 버튼. 둘 중의 하나가 '늘어나야'한다!

조건4 (허깅): 입력창은 'ID를 입력해주세요' 길이 이상으로 늘어나면 안 돼. (우선순위 = 250)
조건5 (허깅): 버튼은 'Button' 길이 이상으로 늘어나면 안 돼. (우선순위 = 250)

따라서 현재 문제는 이 두 조건 중에 무엇을 더 '우선시'할지 몰라서 일어나는 문제다.

인터페이스 빌더를 보면 이런 에러 메시지가 떠있다.
이제 무슨 뜻인지 이해가 된다.

어느 한쪽의 우선순위를 조정해서, 뭐가 '늘어나야' 할지 알려달라는 뜻이다.

그렇다면 뭐가 늘어나도록 해야할까?

이건 디자인적인 결정이지만, 상식적으로 생각해보면 남는 공간이 있을 때
버튼이 길게 늘어나는 것보다는 텍스트 입력창이 길게 늘어나는 게 맞는 거 같다.

버튼의 콘텐츠 허깅 우선순위를 251로 높여준다.
에러가 사라지면서 레이아웃이 완성된다.

컴프레션 우선순위 정하기

이번엔 텍스트 입력창의 콘텐츠 길이를 키우면 어떻게 되는지 보자.

텍스트 입력창의 고유 콘텐츠 사이즈는 Placeholder 텍스트의 양이 결정한다.
'ID를 입력해주세요' 텍스트를 여러번 입력해서 양을 늘려보았다.

다시 X축 에러가 등장했다.

이번에는 주어진 조건을 만족하기 위해서,
두 개의 UI중 어느 한쪽이 콘텐츠 사이즈보다 '줄어들어야'하는 상황이다.

조건1 (컴프레션 저항): 입력창은 'ID를 입력해주세요(X3)' 길이 이하로 '줄어들면' 안 돼. (우선순위 = 750)
조건2 (컴프레션 저항): 버튼은 'Button' 길이 이하로 '줄어들면' 안 돼. (우선순위 = 750)

둘 다 줄어들면 안되는 조건인데, 우선순위가 750으로 똑같다.
그래서 에러가 뜬다.

전 예시와 마찬가지로, 한쪽의 우선순위를 높여주면 해결된다.
버튼이 줄어들면 무슨 버튼인지 모를 수가 있으니까,
이번에도 텍스트 입력창이 양보를 해야할 거 같다.

버튼 컴프레션 우선순위를 751로 높여주었다.

빨간 줄이 사라졌다!


허깅과 컴프레션 우선순위 조정은 자칫 머리를 아프게 만들 수 있는 부분 중 하나다.
하지만 이것만 기억하면 그렇게 어렵지 않다.

내가 설정한 조건보다 콘텐츠 사이즈 조건이 작을 때 (공간이 부족)
-> 잘리면 안되는 UI컴프레션 우선순위를 높인다.
내가 설정한 조건보다 컨텐츠 사이즈 조건이 클 때 (공간이 남음)
-> 늘어나면 안되는 UI허깅 우선순위를 높인다.


요약 정리

  • 오토레이아웃은 뷰에 담긴 콘텐츠 사이즈를 자동으로 계산해서 크기 조건을 설정한다.

  • UIKit에는 버튼, 레이블 같이 고유 콘텐츠를 포함하는 뷰도 있고, UIView 같이 고유 콘텐츠가 없는 뷰도 있다.

  • 콘텐츠 사이즈 조건은 총 4개의 부등식으로 설정된다. (X축 허깅/컴프레션 + Y축 허깅/컴프레션)

  • 허깅은 콘텐츠 사이즈보다 늘어나지 않으려고 하는 조건이다.

  • 컴프레션은 콘텐츠 사이즈보다 줄어들지 않으려고 하는 조건이다.

  • 조건 우선순위는 허깅이 기본 250, 컴프레션은 750으로 설정된다.

  • 콘텐츠 사이즈 조건이 서로 충돌할 때는 우선순위를 높이거나 낮춰서 해결한다.

profile
개발 지식을 쉽고 재미있게 설명해보자.

0개의 댓글