UITableView에 대해
- UIKit
- Swift
🤔 Table View란?

UITableView에 대해 학습하기 전, UIKit에서의 Table View의 정의가 궁금했다.
Dispay data in a single column of customizable rows.
공식 문서에서의 Table View는 커스터마이징이 가능한 단일 열의 데이터 표시 행이라고 한다.

이때 단일 열이라는 점에서 CollectionView와의 차이가 있다고 생각이 든다.
또 테이블 뷰는 세로로 컨텐츠를 스크롤 할 수 있으며 행과 섹션으로 구분된다.
문서에서의 TableViews 는 다음과 같은 객체들간의 집합이라고 말한다.
- Cell
→ 테이블 뷰에 데이터를 시각적으로 표시하기 위한 각각의 행을 말한다.
→ UIKit에서 제공하는 기본 셀 디자인을 사용하거나 사용자 지정 셀을 정의할 수 있다.
- TableViewController
→ https://developer.apple.com/documentation/uikit/uitableviewcontroller
→ TableView를 관리하기 위해 사용되는 컨트롤러이다.
→ 데이터 소스 객체이며 이 프로토콜을 채택함으로써 테이블에 표시하기 위한 데이터를 바인딩 할 수 있다.
- Delegate
→ https://developer.apple.com/documentation/uikit/uitableviewdelegate
→ 델리게이트 객체이며 이 프로토콜을 채택함으로써 테이블 컨텐츠와의 상호작용을 관리할 수 있다.
ViewController에서 TableView를 사용하기 위해선 DataSource와 Delegate를 채택해야한다.
📌 UITableViewDataSource

UITableViewDataSource가 하는 일은 아래와 같다.
- 표의 섹션과 행의 수를 보고합니다.
- 표의 각 행에 대한 셀을 제공합니다.
- 섹션 헤더와 푸터에 제목을 제공합니다.
- 테이블의 인덱스가 있는 경우 이를 구성합니다.
- 기본 데이터를 변경해야 하는 사용자 또는 테이블에서 시작한 업데이트에 응답합니다.
즉, TableView가 어떤 데이터를 표시할지 제공해주는 프로토콜이 DataSource 이다!
UITableView를 사용하기 위해 반드시 채택해야하는 프로토콜이다.
DataSource를 채택할 시 필수로 구현해야하는 메서드는 다음과 같다.


numberOfRowsInSection은 각 세션별 행(row)의 개수를 반환한다!
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
items.count
}
이때, numberOfSections 을 통해 섹션의 개수를 설정할 수 있다.
하지만 필수는 아니며 기본값은 1이다.
cellForRowAt은 테이블뷰가 특정 위치(indexPath)에 표시할 셀을 요청 했을 때 그 셀을 만들거나, 재사용하여 되돌려주는 역할을 수행한다.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as? CustomTableViewCell else {
return UITableViewCell()
}
cell.configure(with: items[indexPath.row])
return cell
}
이때 셀을 만들거나 재사용한다는 의미는 dequeueReusableCell() 등을 사용하여 레지스터에 등록해둔 ReuseIdentifier 에 해당하는 셀을 큐에서 꺼내어 생성하거나 재활용함을 의미한다!
📌 UITableViewDelegate

UITableViewDelegate가 하는 일은 다음과 같다.
- 사용자 정의 헤더 및 푸터 보기를 만들고 관리합니다.
- 행, 머리글, 바닥글에 사용자 정의 높이를 지정합니다.
- 더 나은 스크롤 지원을 위해 높이 추정값을 제공합니다.
- 행 내용을 들여쓰기합니다.
- 행 선택에 응답합니다.
- 테이블 행에서 스와이프 및 기타 작업에 응답합니다.
- 표의 내용 편집을 지원합니다.
즉, TableView의 행위 혹은 UI 관련 이벤트와 인터랙션 제어를 담당한다.
대표적으로 셀이 선택되었을 때 이벤트, 각 셀의 높이, 헤더와 푸터 등 UI를 커스텀할 때 활용된다.
Delegate의 경우 채택했을 때 필수로 구현해야하는 메서드는 없지만 대표적으로 활용되는 메서드는 heightForRowAt과 didSelectRowAt이 있다.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80 // 원하는 높이
}
heightForRowAt의 경우 각 셀의 높이를 지정할 수 있으며 기본값은 tableView.rowHeight 값이다.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("\(indexPath.row)번째 셀이 선택되었어요!")
}
didSelectRowAt은 사용자가 특정 셀을 탭했을 때, 클릭된 탭의 index를 가져온다.
📌 직접 만들어보기
백문이 불여일견. 직접 만들어보면서 학습해보자.
아래 예제는 100% 코드베이스 기반으로 작성하였습니다. 😉
1. ViewController에 TableView 인스턴스 생성 후 바인딩
//
// ViewController.swift
// TableViewStudy
//
// Created by 윤대현 on 9/16/25.
//
import UIKit
class ViewController: UIViewController {
private let tableView = UITableView()
// 테이블뷰에 표시할 더미데이터
private var items: [String] = [
"1번째 셀",
"2번째 셀",
"3번째 셀",
"4번째 셀",
"5번째 셀"
]
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
setupTableView()
}
private func setupUI() {
self.view.backgroundColor = .black
self.title = "제목"
view.addSubview(tableView)
// Auto Resizing Mask 기능 끄기 (오토 레이아웃을 위해!)
tableView.translatesAutoresizingMaskIntoConstraints = false
// 상단은 safe area, 나머지는 화면 꽉차게
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
}
private func setupTableView() {
tableView.dataSource = self
tableView.delegate = self
// 재사용할 셀의 타입을 String 형태의 identifier로 변환해 레지스터 저장
tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: "CustomCell")
}
}
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as? CustomTableViewCell else {
return UITableViewCell()
}
cell.configure(with: items[indexPath.row])
return cell
}
}
2. UITableViewCell 작성
//
// CustomTableViewCell.swift
// TableViewStudy
//
// Created by 윤대현 on 9/16/25.
//
import UIKit
class CustomTableViewCell: UITableViewCell {
let titleLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 16)
label.textColor = .label
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupUI()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupUI()
}
private func setupUI() {
contentView.addSubview(titleLabel)
NSLayoutConstraint.activate([
titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
titleLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8)
])
}
func configure(with text: String) {
titleLabel.text = text
}
}
위 코드를 실행하면 아래와 같이 나온다!

3. TableView 스타일 설정
이때 ViewController에서 private let tableView = UITableView()을 통해 UITableView를 생성했는데, 인스턴스를 생성할 때 따로 생성자를 넘겨주면 TableView의 스타일을 설정할 수 있다.
UITableView의 생성자(init(frame:style:) 에 넘길 수 있는 값은 다음과 같다.

실제 코드로 어떤 차이가 있는지 확인해보자!



기본 값은 plain으로 보인다. 다만, grouped과 plain 차이는 패딩과 배경색이 살짝 회색으로 변하는 거 빼고는 차이가 없는 것 같다.
확실히 디자인 적으로 예쁜 스타일은 insetGrouped인 것 같다.
4. 섹션 구분하기
약간 심화로 들어가서 섹션의 개수를 3개로 나누고, 각각 섹션에 셀을 넣어보자.
private let header: [String] = [
"섹션 1",
"섹션 2",
"섹션 3"
]
ViewController 내부에 섹션의 헤더로 표시하기 위한 문자열 배열을 선언 한 뒤 numberOfSections과 titleForHeaderInSesction 을 통해 섹션을 구분지어보자.
func numberOfSections(in tableView: UITableView) -> Int {
header.count
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
header[section]
}
스타일을 .insetGrouped로 했을 때 실행 결과는 다음과 같다.

5. 셀의 높이와 클릭 이벤트
이번엔 UITableViewDelegate를 사용하여 각 셀의 높이와 클릭 시 셀이 초록색으로 보이도록 해보자.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.cellForRow(at: indexPath)?.backgroundColor = .green
}
다음과 같이 동작함을 확인할 수 있다!

6. 셀 스와이프
셀을 오른쪽이나 왼쪽으로 스와이프 했을 때 버튼이 나오는 동작을 종종 볼 수 있다.
그 동작을 구현하기 위해선 Delegate 에 존재하는 trailingSwipeActionsConfigurationForRowAt와 leadingSwipeActionsConfigurationForRowAt 을 구현하면 된다.
trailingSwipeActionsConfigurationForRowAt: 오른쪽 → 왼쪽leadingSwipeActionsConfigurationForRowAt: 왼쪽 → 오른쪽 스와이프
반환되는 타입은 UISwipeActionsConfiguration이므로 UIContextualAction을 사용해 구현하면 된다.
// 오른쪽 -> 왼쪽 스와이프
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let modity = UIContextualAction(style: .normal, title: "수정") { (UIContextualAction, UIView, success: @escaping (Bool) -> Void) in
print("수정 클릭")
success(true)
}
modity.backgroundColor = .systemBlue
let delete = UIContextualAction(style: .normal, title: "삭제") { (UIContextualAction, UIView, success: @escaping (Bool) -> Void) in
print("삭제 클릭")
success(true)
}
delete.backgroundColor = .systemRed
return UISwipeActionsConfiguration(actions:[delete, modity])
}

반환될 때 actions: []이 있는데 actions 배열의 index가 0인 요소가 우측에 붙는다.
실제 실행 화면은 다음과 같다.

🤔 UITableView와 UITableViewController의 차이는 뭘까?
평소 테이블뷰를 구현하기 위해 ViewController에 TableView 인스턴스를 생성한 뒤, DataSource와 Delegate를 코드로 연결해서 사용을 많이 한다.
문뜩 UIViewController에 UITableView 를 사용하는 방식과 애초에 UITableViewController을 사용하는 방식이 어떤 차이점이 있는지 궁금해서 UITableViewController의 정의를 공식 문서에서 찾아보았다.
TableViewController는 테이블뷰의 컨텐츠를 관리하고 대응할 때 필요한 프로토콜을 모두 채택하고 있다.
그 외에도 아래와 같은 요소들이 있다.
- 셀 선택/해제 시 UI 자동 처리
- ScrollView 이벤트 자동 처리
- Pull-to-Refresh 등 TableView를 커스텀하기 위한 기능을 쉽게 구현할 수 있음
또한 Static Cell과 같은 정적 셀을 구현하기 위해선 반드시 UITableViewController를 사용해야한다고 한다.
즉.. 정적 셀을 구성하기 위해선 UITableViewController를 사용해 구현해야하고, 동적 셀을 구현하기 위해선 ViewController + UITableView 방식과 UITableViewController 모두 활용할 수 있다고 이해했다.
실제 UITableView 방식으론 Static Cell을 구현할 수 없는지 확인해보자.

새로운 프로젝트를 생성한 뒤 ViewController에 UITableView를 끼워넣었다.
그 뒤 인스펙터에서 속성을 Static Cells로 변경하니 다음과 같은 오류가 출력되며 정적 셀을 구성하기 위해선 UITableViewController 을 사용해야 한다는 걸 확인할 수 있었다.

최종적으로 ViewController + UITableView 방식과 UITableViewController 의 차이점을 간단하게 표로 작성한 결과는 다음과 같다.
| 구분 | UITableViewController | UIViewController + UITableView |
|---|---|---|
| Delegate / DataSource | 자동 연결 (별도 지정 불필요) | 직접 지정 필요 (tableView.dataSource = self) |
| 셀 선택/해제 처리 | 자동 반응 (선택 상태 유지/해제 관리) | 선택/해제를 직접 구현해야 함 |
| ScrollView 관련 처리 | 자동 처리 (키보드, inset 조정 등 기본 제공) | 별도 코드 작성 필요 |
| Pull-to-Refresh | refreshControl 프로퍼티로 바로 적용 가능 | UIRefreshControl을 추가 후 수동 연결 필요 |
| 구조 단순성 | 테이블만 있는 단순 화면에 적합 | 기본 화면 구조를 빠르게 구축 가능 |
| 다른 UI 배치 | 제약적 (테이블 외 UI를 추가하기 까다로움) | 자유롭게 다양한 View와 혼합 가능 |
| 확장성 | 단순한 목록 뷰에 유리 | 복잡한 화면 구성, 오버레이, 상태 뷰 추가에 유리 |
| 복수 TableView 지원 | 기본적으로 1개 테이블에 최적화 | 여러 개의 TableView/CollectionView 혼합 가능 |
| 추천 사용 상황 | 단순 리스트, 빠른 프로토타입 | 실제 서비스용, 복잡한 UI/비즈니스 로직 필요 시 |