[iOS] - UICollectionViewFlowLayout spacing에 대한 고찰


UICollectionViewFlowLayout에서 minimum spacing을 적용할 때 일어나는 변화에 대해 알아보자.


UICollectionView를 이용해서 아래와 같이 가로로 아이템을 스크롤 할 수 있는 뷰를 만들고 있었다.

image

UICollectionViewFlowLayout의 minimumLineSpacingminimumInteritemSpacing은 값을 따로 지정하지 않은 상태이므로 둘 다 default 값인 10인 상태이고, 이때 UICollectionView의 contentSize를 찍어보니 아래와 같았다.

image

  • width: 914
  • height: 120

참고로 한 아이템의 가로 길이는 74이다. 여기에서 세로 길이는 별로 중요하지 않아서 높이에 대해서는 자세히 보지 않겠다. 그리고 아이템이 11개이며, 아이템 사이의 간격은 디폴트가 10이므로 74(아이템 너비) * 11(아이템 개수) + 10(아이템 사이의 공백) * 10(공백 개수) = 814 + 100 = 914이 되므로 contentSize의 width가 914로 나온 것은 정확하게 사이즈가 잘 계산되었다는 것을 의미한다. Spacing에 따라 contentSize에 어떻게 달라지는지 확인하기 위해 추가적인 작업들을 해봤다.

레이아웃을 정의할 때 UICollectionViewFlowLayout을 사용했는데, 이 상태에서 아이템 간의 간격을 더 좁히기 위해 아래와 같이 코드를 작성했다. 참고로 아무런 값을 설정하지 않았을 때 기본 값은 10.0이다.

collectionViewLayout.minimumLineSpacing = 4

UIScrollView의 scrollDirection이 horizontal인 상태라 lineSpacing을 변화시켰다. 위와 같이 설정하고 실행시켰더니 아래와 같이 오른쪽에 추가 여백이 생기는 것을 확인할 수 있었다.

image

참고로 이때 collectionView의 contentsize를 출력하니 아래와 같이 나왔다.

image

  • width: 914
  • height: 120

spacing을 변경해서 contentSize가 줄어든 spacing에 맞춰 width가 줄어들 것으로 예상했으나, contentSize는 변하지 않았다. contentSize가 변하지 않은 상태로 lineSpacing은 10에서 4로 줄어들었으니 오른쪽에 여백이 생기는 것은 당연해 보인다. 그렇다면 lineSpacing을 반대로 늘리면 어떻게 될까?

collectionViewLayout.minimumLineSpacing = 20

image

마지막 11번째 아이템이 노출되지 않았다.

이 상태로 contentSize를 찍어보니 아래와 같이 나왔다.

image

  • width: 914
  • height: 120

마찬가지로 contentSize는 변하지 않고, 화면에서 보이는 아이템 사이의 간격만 변했다. 그래서 contentSize의 너비는 일정한데 아이템 사이의 공백 간격이 커져서 마지막 아이템이 보이지 않게 되는 것이다.

lineSpacing은 그렇다 치고, interitemSpacing을 변화시키면 어떻게 될까? lineSpacing을 설정한 코드를 삭제하고, 아래와 같이 코드를 작성했다. InterItemSpacing을 10에서 4로 줄였다.

collectionViewLayout.minimumInteritemSpacing = 4

image

이 친구도 마지막 아이템이 잘려서 출력된다.

그리고 contentSize를 찍어보니 아래와 같이 나왔다.

image

  • width: 854
  • height: 120

contentSize가 바뀌었다! 914에서 854로 줄어들었는데, 60만큼 줄어들었다. 아이템 간의 간격을 10에서 4로 조정했는데, 60만큼 줄어들었다는 것은 감소된 6만큼이 10번 줄어들었다는 것이다. 즉 줄어든 아이템 사이의 간격에 맞게 contentSize가 잘 줄어들었다는 것이다. contentSize가 줄어들었는데 아이템 사이의 spacing(lineSpacing: 여기서는 scrollDirection이 horizontal이므로 line의 기준이 열방향이므로 아이템 사이의 간격을 lineSpacing이라고도 할 수 있다.)은 10이므로 마지막 아이템이 잘려 출력되는 것이 맞다.

그렇다면 반대로 spacing을 늘리면 어떻게 될까?

collectionViewLayout.minimumInteritemSpacing = 20

image

마지막에 여백이 노출되어 출력이 된다.

그리고 contentSize를 찍으니 아래와 같이 나왔다.

image

  • width: 1014
  • height: 120

여기에서도 마찬가지로 contentSize에 변화가 생겼다. 아무것도 설정하지 않았을 때 가로 길이가 914였고, 여기에서는 1014가 되었으니 100만큼 늘어난 것이다. 늘어난 spacing 10만큼 10번이 늘어난 것이고, 여기에서도 마찬가지로 아이템 사이의 변화된 간격이 contentSize에 잘 반영이 된 것을 확인할 수 있다. 하지만 lineSpacing은 그대로 10을 유지하고 있어서 화면 상에는 아이템 사이의 간격이 10으로 나오지만, 실제 contentSize는 늘어났으므로 늘어난만큼 오른쪽에 여백이 노출된 것이다.

그래서 위의 작업들을 통해 횡스크롤 1줄 짜리의 collectionView에서는 minimumInteritemSpacing는 실제 contentSize에 영향을 주지만 화면 상에 보이는 아이템 사이의 실제 간격에는 영향을 주지 않고, 반대로 minimumLineSpacing은 실제 contentSize에 영향을 주지 않지만 화면 상에 보이는 아이템 사이의 간격(여기에서는 아이템 사이의 간격이 lineSpacing이 맞다.)에는 영향을 준다는 것이다.

즉 내가 원하는 대로 아이템 사이의 간격에도 변화를 주고 정상적으로 화면에 아이템이 보이게끔 하려면 아래와 같이 minimumInteritemSpacingminimumLineSpacing의 값을 모두 변화시켜야 한다는 것이다.

collectionViewLayout.minimumLineSpacing = 4
collectionViewLayout.minimumInteritemSpacing = 4

여기서 잠시 minimumInteritemSpacingminimumLineSpacing의 개념을 짚고 넘어가면 아래와 같다.

minimumInteritemSpacing

image

같은 줄안에 있는 아이템 간의 최소 간격을 의미한다.

세로로 스크롤하는 그리드에서 이 값은 같은 줄(행이 될 것이다) 내의 아이템 사이의 최소 간격을 의미한다. 가로로 스크롤하는 그리드에서 이 값은 같은 줄(열) 내의 최소 간격이 될 것이다. 이 간격은 한 줄에 얼마나 많은 아이템이 들어갈 수 있는 지 계산하는데 쓰인다.

minimumLineSpacing

image

줄 간의 최소 간격을 의미한다. InteritemSpacing이 같은 줄 내의 아이템 사이의 간격이었다면, lineSpacing은 줄 간의 간격이 된다.

세로로 스크롤하는 그리드에서 이 값은 연속하는 줄(행) 간의 최소 간격이 될 것이고, 가로로 스크롤하는 그리드에서 이 값은 연속하는 줄(열) 간의 최소 간격이 될 것이다. 이 간격은 헤더와 첫 번째 줄, 그리고 마지막 줄과 푸터에 적용되는 값은 아니다.

여기까지 봤을 때, 가로로 스크롤하는 collectionView에서의 간격을 그림으로 그려보자면 아래와 같다.

image

위의 그림을 봤을 때, 지금까지 작업했던 1줄짜리 collectionView에서 아이템 사이의 간격(lineSpacing)을 늘리기 위해서는

collectionViewLayout.minimumLineSpacing = 20

위와 같이만 작성해도 될 것처럼 보인다. 하지만 실제로 위와 같이만 작성했을 때, lineSpacing이 contentSize에 반영되지 않아 contentSize는 그대로인 반면에 화면에 보이는 lineSpacing은 증가했기 때문에 아이템이 잘려보이는 현상이 발생했다. 그렇다면 왜 minimumLineSpacing은 contentSize에 반영이 되지 않고 minimumInteritemSpacing이 contentSize에 반영이 되는 것일까? 그리고 작업한 collectionView는 한 줄짜리라 interitemSpacing을 변경해도 contentSize의 width가 아닌 height에 적용되어야 할 것 같은데, 왜 width가 늘어나는 것일까?

대체 왜 이런 현상이 일어나는지 찾아보기 위해 UICollectionView가 spacing을 가지고 contentSize를 계산하는 방법에 대해 구글링을 해봤으나 관련된 공식문서나 자료를 많이 찾을 수 없었다. 다만 내가 겪은 현상과 똑같은 현상에 대해 질문한 stackOverflow의 글을 찾을 수 있었다. 하지만 여기에서도 명확한 답을 찾을 수는 없었다.

없으면 답답한 사람이 직접 찾아야 한다고 collectionView의 scrollDirection이 horizontal과 vertical일 때 spacing의 변화가 contentSize에 어떤 영향을 미치는지 직접 알아보기로 했다. ^__^

Vertical Scroll UICollectionView

먼저 익숙한 세로로 스크롤링 되는 UICollectionView를 만들고, 화면 밑에 눌렀을 때 collectionView의 contentSize를 출력하게 하는 버튼을 추가했다. UICollectionView는 남은 화면에 꽉 채워지게 설정했다.

image

이 상태에서 contentSize를 확인하니 아래와 같이 출력되었다.

image

참고로 저 아이템은 50 x 50 인데, 높이를 보았을 때 50 * 3 + 10(linespacing) * 2 = 170이므로 contentSize의 높이가 170이 나오는 것이 맞다.

이제 lineSpacing을 먼저 바꿔보자. 변화를 알아보기 쉽게 lineSpacing을 50으로 설정했다.

image

그리고 contentSize를 출력하니 아래와 같았다.

image

50 * 3 + 50 * 2 = 250이므로 높이가 250이 되는 것이 맞다.

이번에는 itemSpacing을 바꿨다. lineSpacing을 다시 기본값으로 바꾸고, interitemSpacing을 50으로 설정했다.

image

image

interItemSpacing이 50으로 설정되어 있으니 한 줄에는 최대 4개의 아이템이 들어가는 것이 맞다. 그리고 변경된 interItemSpacing에 따라 contentSize의 height도 290으로 잘 조정되었다.

이번에는 lineSpacing과 interItemSpacing을 모두 50으로 설정해줬다.

image

contentSize는 아래와 같다.

image

높이를 봤을 때, 50 * 5 + 50 * 4 = 450이므로 contentSize가 제대로 출력이 된 것이 맞다.

Horizontal Scroll UICollectionView

화면 캡처를 찍고 나서 알았는데 horizontal이 vertical로 잘못 나왔다. 다시 찍기는 귀찮으니 그냥 무시하자.

image

아이템 사이즈가 50 x 50일 때의 화면이다. vertical scroll UICollectionView와 달라진 점은, vertical grid에서는 한 행이 채워지면 다음 행으로 넘어가는데, horizontal grid에서는 한 열이 먼저 채워지면 다음 열로 넘어가는 것이다. 즉 줄의 기준이 행에서 열로 바뀐 것을 알 수 있다.

이 때 contentSize를 출력하니 아래와 같았다.

image

가로의 길이는 50 * 2 + 10 = 110이 되는 것이 맞다.

이제 lineSpacing을 50으로 설정했다.

image

contentSize를 출력하니 아래와 같았다.

image

  • width: 110
  • height: 701

contentSize의 width가 변하지 않았다. 하지만 화면에 실제 보이는 것으로는 lineSpace가 증가한 것이 맞고, 심지어 둘 째 줄의 아이템 사이의 간격이 증가한 형태로 나타났다.

이번에는 설정했던 lineSpacing을 다시 기본값으로 설정하고 interItemSpacing을 50으로 설정했다.

image

contentSize를 출력하니 아래와 같았다.

image

  • width: 250
  • height: 701

contentSize의 width가 바뀌었다. 50 * 3 + 10 * 2 = 170이 되어야 하는 듯 하나 실제로 width는 250이 나왔다. 확실히 scrollDirection이 horizontal일 때 contentSize의 계산이 예상치 못한 방향으로 동작하는 것을 확인할 수 있다.

이번에는 lineSpacing과 interItemSpacing을 모두 50으로 설정해줬다.

image

앞서 봤던 상황처럼 lineSpacing와 interItemSpacing을 각각 설정했을 때 예상하지 못한 방향으로 아이템이 화면에 출력되지 않고, 예상한 대로 화면에 출력되었다.

contentSize를 출력하니 아래와 같았다.

image

여기서 유의깊게 봤던 점은 contentSize가 interitemSpacing만 설정했을 때의 contentSize와 같다는 것이다. 다만 lineSpacing과 interItemSpacing의 값을 모두 설정했을 때의 contentSize의 width를 확인하면 50 * 3 + 50 * 2 = 250으로 contentSize가 올바르게 잡힌 것을 확인할 수 있다.

여기까지 테스트한 결과 추측한 바로는,

  1. UICollectionView의 scrollDirection이 horizontal일 때 minimumLineSpacing의 값은 contentSize에 영향을 주지 않는다는 것이다. 하지만 화면에 보이는 아이템들의 실제 배치에는 영향을 준다.
  2. UICollectionView의 scrollDirection이 horizontal일 때 minimumInteritemSpacing의 값은 contentSize에 영향을 주지 않는다는 것이다. minimumLineSpacing과 마찬가지로 화면에 보이는 아이템들의 실제 배치에 영향을 주고, 설정되는 contentSize의 width값은 minimumInteritemSpacing에 설정된 값 * (아이템의 가로 줄 수(위에서는 3줄이 될 것이다) + 줄 사이의 공백 수(2)) 가 되는 것으로 보인다.

아이템의 개수를 늘려 테스트를 해봤다.

lineSpacing과 interItemSpacing을 모두 50으로 한 상태에서 아이템의 개수를 늘려 화면에 출력해봤다.

image

5줄이 나왔고, contentSize를 출력하니 아래와 같이 나왔다.

image

가로만 보면 50 * 5 + 50 * 4 = 450이 맞다.

이번에는 interItemSpacing만 50으로 설정해서 다시 화면에 출력해봤다.

image

그리고 contentSize를 출력했더니 아래와 같았다.

image

추측했던 대로 contentSize의 width가 잡혔다. 이 가로 길이가 화면의 가로 길이보다 크게 잡혀서 실제로 아래와 같이 가로로 조금 스크롤이 가능하다.

image

이번에는 lineSpacing만 50으로 잡아봤다.

image

아이템들 몇 개가 짤려서 화면에서 보이지 않는다. contentSize를 출력하니 아래와 같았다.

image

contentSize의 width가 170이라 가로가 170을 넘어가는 지점부터 아이템들이 출력이 안되는 것이다.

이렇게 contentSize가 잘못 나올 때의 해결 방법은 더 찾아보고 이어서 써보도록 하겠다.