세 번째 이자 마지막으로 채팅 목록 앱을 만들어보겠습니다. 목록을 보여주는 앱은 너무나도 다양하죠. 앱에서 리스트 형식의 UI를 보여주는 방법은 두 가지가 있습니다.

 

1. UITableView

2. UICollectionView

 

이 두 개의 View는 경우에 따라 사용할 수 있습니다. 만약, 아이폰의 연락처 앱과 같이 화면에 가득 찬 리스트로 이뤄진 UI라면 UITableView가 적합합니다. 하지만 인스타그램 앱처럼 그리드 형식의 리스트를 보여준다면 UICollectionView가 적합합니다.

두 View 모두 Cell이라는 개념을 사용하는데요, 이 Cell은 리스트 하나하나를 의미합니다. 

앱을 만들어보며 리스트가 무엇인지, 알아보겠습니다.

이번 앱은 제가 최근 자주 사용하는 ‘당근 마켓’ 앱을 모티브로 한 물품 거래 앱을 아래와 같이 만들어보겠습니다.

 

이미지에 대체텍스트 속성이 없습니다; 파일명은 20210126_110536_1.png 입니다.

 

우선 지난 앱과 마찬가지로 프로젝트를 생성합니다.

 

이미지에 대체텍스트 속성이 없습니다; 파일명은 20210126_110536_2.png 입니다.

 

저는 간단하게 ProductList라는 프로젝트 이름으로 생성했습니다.

그다음은 위 캡처 화면에서 위치를 나타내는 Label과 리스트를 나타내는 View를 그려야 합니다.

리스트를 나타내는 View를 이 프로젝트에선 UITableView를 사용하겠습니다.

 

이미지에 대체텍스트 속성이 없습니다; 파일명은 20210126_110536_3.png 입니다.

 

우선 Label의 경우 저는 System SemiBold 17.0 Font로 사용하였으며, Leading 20, Top 10으로 제약조건을 설정한 상태입니다.

그다음은 리스트와 UILabel사이에 경계선을 추가해야겠죠. 경계선의 경우 색상이 System Gray Color인 UIView를 추가합니다. 이 경계선의 위치는 Leading, Trailing은 superview로부터 0pt만큼 떨어져 있고, 상단 UILabel로부턴 10pt, 그리고 height는 1pt로 설정합니다.

(제약조건은 x, y, width, height를 모두 충족해야 합니다.)

그리고 UITableView를 Library (cmd + shift + L)에서 끌어와 화면에 추가합니다. UITableView의 경우 Leading, bottom (SafeArea), trailing 0pt로 설정하고 경계선으로부터 0pt 만큼 떨어지도록 제약조건을 설정합니다.

이제 UITableViewCell을 추가해야 합니다. 위에서 리스트의 하나하나는 Cell이라고 설명했습니다. 중요한 작업이겠죠. UITableView와 마찬가지로 Library에서 UITableViewCell을 UITableView위에 추가합니다.

 

이미지에 대체텍스트 속성이 없습니다; 파일명은 20210126_110536_4.png 입니다.

 

이 곳에서 리스트의 height는 140pt로 설정하고 싶기 때문에, 한 가지 설정이 필요합니다.

 

이미지에 대체텍스트 속성이 없습니다; 파일명은 20210126_110536_5.png 입니다.

 

바로 UITableView를 클릭하고 Size Inspector(우측 6번째 영역)에서 Row Height를 140pt로 설정하고 Estimate와 Row Height의 Automatic 옵션을 해제합니다. 이 옵션이 켜져 있으면 Cell 하위 View의 제약 조건에 따라 자동으로 Cell의 높이를 계산하는데요, 현재는 140pt로 높이를 고정하고 싶기 때문에 Automatic 옵션을 해제합니다.

이제 Cell을 꾸며야 합니다. Cell에 우선 UIImageView를 추가합니다. 제약조건을 설정할 때 가장 중요한 것은 x, y, width, height을 설정하는 것입니다. UIImageView를 추가한 후 leading 20pt, (top, bottom) 20pt로 설정합니다. 이후 width, height을 1:1로 설정할 시 (aspect ratio) x, y, width, height의 조건을 모두 만족하죠.

그리고 UIView의 이미지는 임시로 아무거나 설정합니다. 저는 iOS13부터 기본으로 제공되는 아이콘 중 scribble 이미지를 사용하겠습니다.

 

이미지에 대체텍스트 속성이 없습니다; 파일명은 20210126_110536_6.png 입니다.

 

그다음은 타이틀, 주소, 가격 Label을 추가해야 합니다. 타이틀은 UIImageView와 20pt만큼 떨어져 있고 Top은 UIImageView와 동일하게 설정합니다. 이렇게 설정하면 제약조건은 완성되죠. Y값과 X값만 설정했고 Width와 Height은 설정하지 않았는데 어떻게 되는지 궁금하신가요? 

그 이유는 바로 intrinsic Content Size 때문입니다. UILabel, UIImageView 등 일부 View는 고유 크기(Size)를 가지게 됩니다. 즉, Width와 Height는 제약조건을 설정해주지 않는다면 자신의 고유 크기 값으로 설정된다는 의미입니다.

타이틀과 동일하게 주소와 가격 UILabel을 추가해야 하는데요, Leading 값만 타이틀과 동일하게 설정한 후 적절한 값으로 아래와 같이 UI를 구성합니다. (색상, 폰트도 자유롭게 설정합니다.)

 

이미지에 대체텍스트 속성이 없습니다; 파일명은 20210126_110536_7.png 입니다.

 

이제 Cell의 UI 배치를 완료했으니 가장 중요한 작업이 남았습니다. 바로 리스트를 얼마나 보여줄지, 어떤 값을 보여줄지에 대한 설정입니다. 이 과정에서 프로그래밍의 디자인 패턴 중 Delegation 패턴이 사용됩니다. 

디자인 패턴이란 조금 더 프로그래밍을 적절하게, 깔끔하게 구현할 수 있도록 약속해놓은 방법이라고 생각하면 좋습니다.

이러한 Delegation Pattern이 대표적으로 적용된 사례가 UITableView라고 할 수 있겠습니다. 간단하게 설명하자면 Apple이 만들어놓은 UITableView를 서비스(앱) 마다 어떻게 사용할지 모르기 때문에 Apple이 이렇게 말합니다.

“너네 내가 리스트는 보여줄게, 대신 리스트에 보일 정보나 개수 같은 건 너네가 결정해”

바로 책임을 위임(delegate)하는 것이죠. 코드로 보겠습니다.

 

이미지에 대체텍스트 속성이 없습니다; 파일명은 20210126_110536_8.png 입니다.

 

ListViewController(우리의 Main View Controller)가 UITableViewDataSource를 따르게 됩니다.

아, 따른다 라는 의미는 UITableViewDataSource가 정의해놓은 함수를 반드시 구현해야 한다는 의미입니다.

즉, 애플이 UITableView를 구성하기 위해 리스트에 보일 정보나 개수 같은 것을 요구할 텐데 이 요구를 따르겠다는 의미라고 생각하면 좋겠습니다.

일단 그전에 ItemCell이라는 class가 눈에 띕니다. 이는 리스트를 보여줄 Cell을 담당하게 될 Class입니다. Cell을 담당하기 위해선 애플이 정의해놓은 UITableViewCell이라는 class여야 합니다. 이 조건을 맞추기 위해선 상속이란 개념이 사용되는데, 상속은 이 글에서 확인할 수 있습니다. 이런 조건을 충족하기 위해 UITableViewCell을 상속받는다, 즉 UITableViewCell을 뒤에 붙여주는 것이죠.

이렇게 정의해둔 ItemCell은 Storyboard에도 알려줘야 합니다.

 

이미지에 대체텍스트 속성이 없습니다; 파일명은 20210126_110536_9.png 입니다.

 

스토리보드 상에서 Cell을 클릭하고 ItemCell으로 Class를 설정합니다. 뿐만 아니라 한 가지 작업이 더 필요합니다.

Attribute Inspector를 클릭한 후 Identifier를 ItemCell로 적용하는 것이죠.

(이 부분은 셀 재사용과 관련이 있는데, 우선은 클래스 이름과 동일하게 ItemCell로 설정합니다.)

 

이미지에 대체텍스트 속성이 없습니다; 파일명은 20210126_110536_10.png 입니다.

 

설정이 완료됐다면 다시 ListViewController으로 돌아가겠습니다.

 

이미지에 대체텍스트 속성이 없습니다; 파일명은 20210126_110536_11.png 입니다.

 

두 가지 함수가 보이죠. 하나는 numberOfRowsInSection이라는 파라미터를 가진 함수입니다. 이는 이름으로 봤을 때 Section안의 Row 개수 정도로 알 수 있을 것 같습니다. 

여기서 Section은 무엇이며 Row는 무엇인지 궁금할 수 있습니다.

간단합니다. 글로 따지면 Section은 문단이고 Row는 문장입니다. 여러 개의 문단에 각각의 문장을 설정할 수 있다고 생각하시면 좋겠습니다.

이 앱에선 하나의 문단(section)을 사용하며 그 안에 여러 개의 문장(row)을 추가합니다.

코드 상으로 5라는 값을 반환해주는 것을 알 수 있는데 즉 이 앱에서 사용하는 section의 row 개수는 5개야 라고 UITableView에 알리는 것입니다.

그다음으로 보이는 cellForRowAt이라는 파라미터를 갖는 함수는 하나의 Row당 어떤 Cell을 보여줄 건지를 UITableView에 알려줘야 하는 함수입니다.

여기서 IndexPath는 section과 row로 구성되어있는데, 이 값을 사용해서 어떤 section의 row에서 어떤 cell을 보여줄 것인지 설정할 수 있는 것이죠.

함수 안의 코드는 우선 이해하지 않고 따라치셔도 좋습니다.

이는 위에 잠깐 언급한 셀 재사용과 관련 있는 코드인데요, 이 책은 기초과정이기 때문에 우선은 생략합니다.

재사용을 하는 이유는 리스트 UI이기 때문입니다. 보여줘야 할 목록이 1000개, 10000개, 1000000개가 됐을 때 Cell을 그만큼 만들게 되면 메모리를 많이 사용하기에 내부적으로 Cell을 재사용하는 것입니다.

이 상태에서 한 가지 추가해야 할 코드가 있습니다.

 

이미지에 대체텍스트 속성이 없습니다; 파일명은 20210126_110536_12-1.png 입니다.

 

tableView.dataSource = self 가 무엇을 의미할까요?

위에 언급하였듯 UITableView는 리스트를 구성하기 위해 어떻게 구성할지 다른 클래스에게 위임합니다.

그럼 어떤 클래스에게 위임하는지 UITableView에게 알려줘야 할 텐데요, UITableView는 dataSource라는 프로퍼티를 갖고 있고 UI를 구성할 때 dataSource로 설정된 클래스의 함수를 실행시킵니다.

이 과정을 설정하기 위한 코드가 위 코드입니다.

ListViewController는 위에서 UITableViewDataSource의 역할을 한다고 설정해뒀기에 tableView에겐 dataSource (위임받는 이)가 자기 자신 (self)라고 알려주는 것입니다.

이 상태로 빌드해보겠습니다.

 

이미지에 대체텍스트 속성이 없습니다; 파일명은 20210126_110536_13.png 입니다.

 

다섯 개의 리스트가 동일하게 나타나는 것을 확인할 수 있습니다.

이제 물품 데이터를 리스트에 보여주도록 구현해야 합니다.

이 과정은 잠깐 호흡을 가다듬고 다음 글에서 계속됩니다.

고맙습니다.

 

 

윤민섭님의 브런치에 게재한 글을 편집한 뒤 모비인사이드에서 한 번 더 소개합니다.