TableView란
여러 데이터들을 리스트처럼 스크롤할 수 있게 나타내는 뷰이다. 그렇기 때문에 ScrollView를 상속받는다. 많은 데이터를 나타낼때 모든 데이터를 메모리에 올리지 않고 화면에 나타나는 뷰들만 메모리에 올라가기때문에 메모리 관리에 용이하다.
개념별 예제
- 테이블뷰와 뷰컨트롤러를 연결시킨다.
- 테이블뷰에 필요한 클래스, 프로토콜을 상속받는다. (UITableView를 관리하기 위한 객체로 설정하기 위해서)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class ViewController: UIViewController {
// 더미 데이터
var dummyList = ["1", "2", "3"]
// 테이블뷰 정의
var myTableView: UITableView = {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
return tableView
}()
override func viewDidLoad() {
super.viewDidLoad()
myTableView.dataSource = self
myTableView.delegate = self
myTableView.register(MyCell.self, forCellReuseIdentifier: MyCell.identifier) // 커스텀셀, 셀 id로 테이블뷰에 등록
view.addSubview(myTableView)
NSLayoutConstraint.activate([
myTableView.topAnchor.constraint(equalTo: view.topAnchor),
myTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
myTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
myTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
}
}
- 테이블뷰에 셀 등록
1 2 3
... myTableView.register(MyCell.self, forCellReuseIdentifier: MyCell.identifier) // 커스텀셀, 셀 id로 테이블뷰에 등록 ...
UITableViewDataSource 프로토콜
1
2
3
4
5
6
7
8
9
10
11
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dummyList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: MyCell.identifier, for: indexPath) as? MyCell else { return UITableViewCell() }
cell.label.text = dummyList[indexPath.row]
return cell
}
}
- 셀의 개수를 반환할 코드를 정의한다.
- UITableViewDataSource 프로토콜의 필수 메서드중 하나로, 특정 섹션에 표시할 셀의 개수를 반환하는 역할.
1
2
3
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
dummyList.count // 섹션이 하나라면 그냥 데이터의 개수를 반환
}
섹션이란? 테이블 뷰에서 여러 개의 그룹을 가질 수 있는 개념이다.
- 테이블 뷰의 특정 위치(IndexPath)에 셀을 생성하고 반환하는 역할을 한다.
1
2
3
4
5
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: MyCell.identifier, for: indexPath) as? MyCell else { return UITableViewCell() }
cell.label.text = dummyList[indexPath.row]
return cell
}
dequeueReusableCell : 재사용 큐(Reuse Queue)에서 셀을 가져오는 메서드
indexPath의 역할
cellForRowAt indexPath: IndexPath의indexPath매개변수는 어떤 셀을 생성해야 하는지 위치를 지정하는 역할을 한다.
indexPath.section: 섹션 번호 (어떤 섹션의 셀인지)indexPath.row: 행 번호 (해당 섹션 내에서 몇 번째 행인지)
UITableViewDelegate 프로토콜
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(#fileID, #function, #line, "- ")
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
// 1. indexPath를 사용하여 특정 셀의 데이터에 접근합니다.
let item = dataArray[indexPath.row]
// 2. 데이터에 따라 동적으로 높이를 계산하거나 지정합니다.
// 예를 들어, 텍스트가 길면 더 높은 셀을 반환합니다.
if item.isLongText {
return 120.0 // 긴 텍스트가 있는 셀의 높이
} else {
return 60.0 // 일반 셀의 높이
}
}
}
- 테이블에서 해당 셀이 선택되었을때 호출되는 함수
1
2
3
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(#fileID, #function, #line, "- ")
}
- 셀의 높이를 커스터마이징하는 함수
1
2
3
4
5
6
7
8
9
10
11
12
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
// 1. indexPath를 사용하여 특정 셀의 데이터에 접근합니다.
let item = dataArray[indexPath.row]
// 2. 데이터에 따라 동적으로 높이를 계산하거나 지정합니다.
// 예를 들어, 텍스트가 길면 더 높은 셀을 반환합니다.
if item.isLongText {
return 120.0 // 긴 텍스트가 있는 셀의 높이
} else {
return 60.0 // 일반 셀의 높이
}
}
Cell
- 테이블뷰에 아이템들을 나타내기 위한 요소
- 셀 재사용
- 스크롤에서 사라진 셀은 “재사용 큐”에 보관됨
- 스크롤을 내려 새로운 셀이 필요해지면
UITableView는 데이터 소스에게dequeueReusableCell(withIdentifier:for:메서드로 재사용 가능한 셀 요청 - 셀이 전달되면, UI는 그대로 두고 내부의 데이터만 새롭게 업데이트하여 반환
- 셀 커스텀
UITableViewCell을 상속받는 클래스를 만들어 사용- 여러 ui요소들을 적용 예제 ``` class MyCell: UITableViewCell {
static let identifier: String = “MyCell” // 셀 고유 식별자 정의
let label: UILabel = { let label = UILabel() label.backgroundColor = .gray label.textColor = .black label.translatesAutoresizingMaskIntoConstraints = false return label }()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) contentView.addSubview(label)
1 2 3 4 5 6 7
NSLayoutConstraint.activate([ label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), label.topAnchor.constraint(equalTo: contentView.topAnchor), label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), label.heightAnchor.constraint(equalToConstant: 44) // 셀의 높이를 고정 ]) }required init?(coder: NSCoder) { fatalError(“init(coder:) has not been implemented”) } }
1
2
3
## 객체 데이터 셀별로 입력
1. 객체(class) 정의
class Item { var title: String var description: String
1
2
3
4
init(title: String, description: String) {
self.title = title
self.description = description
} } ```
- 객체를 입력받을
configure(with item:)정의 ``` class MyCell: UITableViewCell { …
func configure(with item: Item) { self.titleLabel.text = item.title self.descriptionLabel.text = item.description }
… }
1
2
3. `UITableViewDatasource`의 `tableView(cellForRowAt:)`함수 수정
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: MyCell.identifier, for: indexPath) as? MyCell else { return UITableViewCell() } cell.configure(with: dummyList[indexPath.row]) // 데이터를 configure를 통해 할당 return cell }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
## 셀 클릭 시 상세페이지로 이동
1. sceneDelegate 수정
- 뷰컨트롤러를 네비게이션컨트롤러로 감싸서 상세페이지로 이동이 가능하도록 설정
- 앱의 라이프사이클을 관리하는 객체인 `UIScene`을 ios 13 이후로는 여러개의 윈도우를 가질 수 있게 되면서 `UIWindowScene`타입으로 캐스팅
```
guard let windowScene = (scene as? UIWindowScene) else { return }
```
- 앱의 콘텐츠를 담을 가장 바깥쪽 컨테이너인 윈도우를 생성
```
let window = UIWindow(windowScene: windowScene)
```
- 실제 뷰 컨트롤러 인스턴스 생성
```
let mainVC = ViewController()
```
- 네비게이션컨트롤러의 rootViewController 할당
```
let navigationController = UINavigationController(rootViewController: mainVC)
```
- 네비게이션컨트롤러를 `window`의 rootViewController로 할당
```
window.rootViewController = navigationController
```
- 새로 생성한 윈도우를 메모리에 유지하기 위해 앱의 속성 `window`에 할당
```
self.window = window
```
- 윈도우를 앱의 주요 윈도우로 만들고 화면에 표시
```
window.makeKeyAndVisible()
```
- 전체 코드
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } let window = UIWindow(windowScene: windowScene) let mainVC = ViewController() let navigationController = UINavigationController(rootViewController: mainVC) window.rootViewController = navigationController self.window = window window.makeKeyAndVisible() }
1
2
3
2. 상세페이지 구현
class DetailViewController: UIViewConroller { … }
1
2
3. `UITableViewDelegate`프로토콜의 `tableView(_ :didSelectRowAt:)`함수 구현(`UINavigationController`로 구현)
extension ViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 탭한 셀의 데이터에 접근합니다.
let selectedItem = dummyList[indexPath.row]
// 다음으로 넘어갈 새 뷰 컨트롤러를 만듭니다.
let detailVC = DetailViewController()
// 다음 뷰에 필요한 데이터를 전달합니다.
// detailVC.data = selectedItem
// UINavigationController가 이 뷰 컨트롤러를 스택에 추가하여 화면을 전환합니다.
// 이 코드가 정상 작동하려면 ViewController가 UINavigationController 안에 포함되어 있어야 합니다.
navigationController?.pushViewController(detailVC, animated: true)
tableView.deselectRow(at: indexPath, animated: true)
} } ```
📌 실행 흐름
- 뷰컨트롤러가 초기화 되며 테이블뷰 생성
viewDidLoad호출되면 테이블뷰에datasource,delegate세팅 / 테이블뷰에 셀 등록- 테이블뷰 constraints 적용
- 델리게이트 패턴으로 인해 테이블뷰 내부에서 특정 함수들을 호출하면 책임을 위임받은 뷰컨트롤러에 정의된 함수들이 실행됨
- 데이터의 개수만큼 정의된
tableview(tableView:cellForRowAt:)함수가 동작하며 정의한 커스텀 셀을 재사용하거나 새로운 셀마다 데이터 입력