[iOS] 테이블 뷰 컨트롤러1(Table View Controller)

LEEHAKJIN-VV·2022년 7월 7일
0

iOS 정리

목록 보기
3/7
post-thumbnail

본 글은 꼼꼼한 재은씨의 Swift 기본 편과 애플 공식 문서를 참고하여 작성한 글입니다. 잘못된 정보가 있을 시 댓글로 남겨주시면 감사하겠습니다. 😎

테이블 뷰 컨트롤러 (Table View Controller)

테이블 뷰 컨트롤러(Table view Controller)는 테이블 뷰를 관리하는 컨트롤러로 뷰 컨트롤러(View Controller)를 상속한다.

테이블 뷰의 계층구조

테이블 뷰의 계층구조는 다음과 같다.

위에서부터 차례대로 설명하면 Table View Controller가 하나의 scene(화면)을 의미한다. 그리고 일반적인 View Controller들은 하나의 root view를 가지는 데, 이처럼 Table View Controller는 루트 뷰로 하나의 Table view를 가진다.

Table view는 mutliple 행을 가질 수 있다. 한 개의 행을 테이블 뷰 셀(Table View Cell)이라고 한다. 그리고 Table View Cell은 내부에 콘텐츠 뷰(Content View)를 가진다. 실제 화면에 보이는 각 행의 내용은 콘텐츠가 콘텐츠 뷰에 배치된 결과이다.

UITableViewCell

UITableViewCell 객체는 테이블 뷰의 행의 내용을 관리하는 특수한 유형의 View이다. 이 클래스를 사용하여 앱의 contents를 표시할 수 있다.

UITableViewCell의 영역은 사용자에게 보여주기 쉽게 몇 개의 영역으로 나누어져 있다. 우선 영역을 설명하기 전에 사용되는 용어를 아래에 기술한다.

  • Cell content: cell에 표현될 콘텐츠
  • Accessory View: 콘텐츠의 부가 정보 여부
  • Cell bounds: cell의 영역

그리고 아래에 몇 개의 영역으로 나누어진 예시다.

UITableViewCell 공식 문서 사진

위 사진처럼 content는 cell의 대부분의 영역을 차지하지만 다른 부가적인 정보를 위해서 Accessory view와 같이 추가적인 뷰를 표시할 수 있다. 또한 테이블을 편집모드로 전환할 시 앞쪽에 삭제 컨트롤을 추가하고, 뒤쪽에 재정렬 컨트롤을 추가할 수 있다.

cell에 contents를 표시하기 위해선, cell에 identifier(식별자)를 부여해야 한다. 이는 아래에 컨트롤러를 실제로 사용하면서 설명한다.

테이블 뷰 컨트롤러 구현하기

이제 실제로 테이블 뷰 컨트롤러를 이용하는 실습을 진행해 보자. 실습 환경은 Xcode13.4이며 코드와 스토리보드 모두 사용한다.

우선 기존의 새로운 프로젝트를 생성하면 Story board에 ViewController가 1개 생성된다. 이를 삭제하고 TableViewController를 추가한다.

테이블 뷰 컨트롤러를 추가한 스토리보드 모습

그리고 추가한 컨트롤러를 시작 컨트롤러로 지정한다.

이제 테이블 뷰에 데이터를 표현하기 위해 클래스(ListViewController)를 생성한다.

이 클래스를 스토리보드의 테이블 뷰 컨트롤러와 연결해야 한다. 그러나 연결하기 전에 테이블 뷰를 다루기 위해서 UITableViewController를 상속한다.

swift파일과 테이블 뷰 컨트롤러의 연결은 스토리보드에서 테이블 뷰 컨트롤러를 선택하고 identity inspector에서 새로 생성한 클래스를 지정하면 된다.

이제 사용자가 원하는 테이블 뷰 셀을 선택한다. 기본적으로 제공되는 4가지 형식과 Custom 형식이 있다.
이는 스토리보드에서 설정할 수 있다.

4가지 스타일은 다음과 같이 표현된다.

Basic

Right Detail

Left Detail

Subtitle

베이직은 기본적인 설명만 있으며, Right는 세부 설명이 오른쪽에, Left는 왼쪽, Subtitle은 아래에 있다. 이는 기본적으로 제공되는 셀 형식이다. 이 형식 말고 다른 형식을 사용하고 싶다면 커스텀 타입을 사용하여 개발자가 원하는 형식으로 변경할 수 있다. 이는 다음 글에서 다루도록 한다.

이번 실습에서는 Subtitle 형식을 사용한다.

이제 사용할 TableViewCell을 클릭하여 identifier를 부여한다. 이유는 하나의 테이블 뷰 컨트롤러에 여러 개의 TableViewCell이 있을 수 있기 때문에 identifier로 식별하기 위해서다.

이제 다음 섹션에서 만든 테이블 뷰 컨트롤러에 우리가 원하는 데이터를 표현하자.

데이터를 테이블 뷰 컨트롤러에 표현하기

테이블 뷰에 데이터를 표현하기 위해서 우리는 데이터의 형식을 정해야 한다. 이번 실습에서 표현할 데이터는 영화 정보이므로, 이를 표현하는 클래스를 새로 생성한다.

클래스의 이름은 MovieVO이며, 클래스 각각의 프로퍼티는 영화에 해당하는 한 개의 정보를 나타낸다. 프로퍼티에 해당하는 정보가 존재하지 않을 수 있기 때문에 프로퍼티를 옵셔널 타입으로 선언한다.

이제 테이블 뷰에 표시할 데이터를 만들자. 데이터는 이전에 새로 만든 클래스 ListViewController에 아래와 같이 만든다.

앱을 어떻게 설계하는냐에 따라 사용자가 앱에서 직접 데이터를 입력하거나, 개발자가 미리 입력하는 경우로 구분된다. 지금은 작동 과정을 확인하기 위해 미리 데이터를 정적으로 구축한다.

우선 dataSet프로퍼티는 테이블 뷰에 표현할 데이터의 정보를 가지고 있는 배열이다. 1개의 영화를 튜플로 묶고 배열에 담는다.

dataSet 프로퍼티를 이용하여 테이블 뷰의 각 행을 만들 때 참조할 리스트 list 프로퍼티를 만든다. 이 프로퍼티에 lazy 키워드가 붙은 것을 확인할 수 있다.

이 키워드를 사용한 이유는 list프로퍼티의 초기화를 위해서 dataSet프로퍼티가 필요하다. 그러나 우리는 list프로퍼티의 초기화 시점에 dataSet 프로퍼티가 먼저 초기화되었다고 보장할 수 없다.

그러므로 lazy 키워드를 사용하여 마지막에 초기화 되도록 지정한다. 또한 키워드를 사용함으로써, 테이블 뷰가 필요할 때 프로퍼티를 초기화하므로 메모리 절약에 관한 측면도 있다. (실제로 같은 클래스 내에 프로퍼티는 lazy키워드가 없으면 서로 참조할 수 없음)

그러나 lazy 키워드를 사용하면 메모리 관리가 어렵다고 글에서 본 적이 있다. 이유는 아직 잘 알지 못해서 앞으로 메모리 처리 관련도 학습해야 겠다 (그러므로 지금은 건너뛰겠습니다.)


이제 우리가 만든 데이터 소스와 테이블 뷰를 연동해야 한다. 이는 UITableDataSource라는 프로토콜에 의존하여 진행이 된다. UITablezViewController가 이 프로토콜을 상속하기 때문에 우리는 프로토콜이 제공하는 메소드로 연동을 하면 된다.

UITableDataSource 프로토콜이란?
테이블 뷰는 데이터 자체를 관리하는 것이 아닌 데이터의 표현(표시)만 담당한다. 데이터를 직접 관리하기 위해서는 UITableViewDataSource 프로토콜을 구현하는 객체인 data source 객체를 사용해야 한다. data source 객체는 테이블의 데이터 관련 요청에 응답하여 데이터를 직접 관리한다. (공식 문서 UITableDataSource 참조)

테이블의 데이터를 관리하기 위해 UITableDataSource 프로토콜이 제공하는 메소드는 2개가 있다. 이 메소드는 UITabeView 클래스에 구현되어 있어 override 하여 사용하면 된다. 이 메소드들은 테이블 행을 구성할 때 시스템이 자동적으로 호출한다.

  • func tableView(_:numberOfRowsInSection:) -> Int

  • func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int

각 메소드에 대해 간단히 설명하면 tableView(_:numberOfRowsInSection:)는 테이블 뷰가 생성해야 할 행(row)의 수를 반환한다. 우리는 우리가 표현할 데이터를 list 프로퍼티에 초기화 시켰기 때문에 배열의 element의 수를 반환하면 된다.

tableView(_:cellForRowAt:) 메소드는 각 행이 화면에 표현해야 할 내용을 구성하는 데 사용된다. 이 메소드의 2번째 파라미터인 indexPath에는 테이블 뷰를 구성할 행에 대한 정보가 담겨있다.


이제 이 메소드를 사용해 보자

이전에 만든 ListViewController클래스에 위에 소개하였던 메소드를 다음과 같이 구현한다.

tableView(_:cellForRowAt:) 메소드에서 2번째 파라미터인 indexPath에서 row 프로퍼티를 참조하는 것을 확인할 수 있다. 이는 현재 구성할 행의 번호를 나타내는 것이다 배열처럼 0부터 시작하므로 이를 배열의 index처럼 사용하면 된다.

테이블 뷰에 사용되는 Row와 Section의 위치

Section 이란 용어는 테이블 뷰는 여러 개의 Section으로 구성된다. 각 Section 안에 여러개의 TableViewCell이 존재한다. 즉 cell이 모여 Section을 이루고, Section이 모여 Table view를 구성하는 것이다. 현 실습에서는 1개의 Section만 다루기 때문에, Section을 생성하거나 설정하는 방법은 다루지 않는다.

여기서 dequeueReusableCell 메소드를 사용하는 것을 확인할 수 있는 데 이는 이름 그대로 cell을 재사용하는 것으로 여기서는 UITableViewCell 객체를 재사용한다.

재사용을 하는 이유는 만약 테이블 뷰의 행의 개수가 100만 개라고 가정하면 우리는 100만 개의 행을 만들어야 하는 데 이는 메모리적으로 매우 비효율적이다. 그러므로 화면에 표현할 만큼의 cell을 만들고 해당 cell이 화면에서 사라지면 다음에 행을 표현하기 위해 이전의 cell을 다시 재사용하는 것이다. (안드로이드의 리사이클러 뷰와 비슷한 듯?) 만약 큐나 리스트에 재사용할 cell이 존재하지 않는다면 해당 메소드는 nil을 반환한다.

다음으로 우리가 이전에 TableViewCell 부여하였던 indentifier를 사용한다. 2번째 파라미터인 withIdentifier에 생성할 cell의 식별자를 할당하면 해당 cell을 생성하기 위해 큐나 리스트에서 이전에 사용되었던 cell이 반환된다.

이제 cell을 반환받았다면 cell의 기본 속성을 사용하여 테이블 행의 내용을 채워야 한다. 아래에 몇 가지 속성들이 있다. 현재는 아래 속성을 사용하지만 다음 글에서는 애플에서 권장하는 UIListContentConfiguration 구조체로 행의 데이터를 표현하는 방법을 다룬다.

  • textLabel 프로퍼티는 위에서 설명한 cell의 기본 형식 중 제목(Title)을 표시
  • detailTextLabel는 상세 설명에 해당
  • imageView는 테이블의 셀에 이미지에 해당

그러므로 textLabel프로퍼티와 detailTextLabelMovieVO 클래스의 title 속성과 description 속성을 할당하여 테이블의 content를 채운다. cell의 객체가 옵셔널 타입이므로 옵셔널 체이닝을 사용한다.


이제 테이블 뷰의 UI를 시각적으로 보기 좋게 하기 위해 네비게이션 컨트롤러를 추가한다. 그런 다음 네비게이션 아이템을 테이블 뷰 컨트롤러 상단에 추가한다. 다음으로 테이블 뷰 아래에 뷰를 추가하여 cell의 빈칸을 표시 하지 않는다.(해당 내용을 빨리 진행하였는데, 만약 어려움이 있는 경우 알려주세요)

완성된 스토리 보드의 모습은 아래와 같다.

다음으로 실제로 앱을 실행시킨 시뮬레이터의 모습이다.


참고 자료

공식문서

https://developer.apple.com/documentation/uikit/uitableviewcell
https://developer.apple.com/documentation/uikit/uitableviewdatasource/
https://developer.apple.com/documentation/uikit/views_and_controls/table_views/filling_a_table_with_data
https://developer.apple.com/documentation/uikit/uitableview/1614891-dequeuereusablecell

자료

꼼꼼한 재은씨의 Swift 기본편

0개의 댓글