본문

  • Article : Filling a Table with Data
  • Article : Configuring the Cells for Your Table

참고


🔫 간단요약

✔️ 구성

4가지 요소

  • UITableViewController
  • UITableViewDataSource
  • UITableViewDelegate
  • UITableViewCell

✔️ 기본적인 구현예시

A: UITableViewController 구현하는 경우
B: UIViewController가 DataSource/Delegate 채택하는 경우
C: 별도의 DataSource/Delegate class 구현하는 경우

구현 순서

  1. 스토리보드에 Table View를 추가
    ▪️ A: 불필요
    ▪️ B,C: 필요

  2. 스토리보드에 prototype cell을 Table View 안에 추가
    ▪️ A: 불필요
    ▪️ B,C: 필요

  3. 스토리보드에서 cell style을 설정
    (Custom cell style이면) Cell에 view를 넣고 오토레이아웃 적용
    (필요하면) Accessory View 설정
    ▪️ A,B,C: 동일

  4. 뷰컨에 IBOutlet으로 table view 연결
    ▪️ A: 불필요
    ▪️ B,C: 필요

  5. UITableViewDataSource class 정의

  • (Section이 여러 개면) numberOfSections(in:) : Section 개수
  • tableView(_, numberOfRowsInSection:) : Section 당 row 개수
  • tableView(_, cellForRowAt:) : Cell data 설정
    ▪️ A: 위 메서드를 전부 override로 구현
    ▪️ B: 위 메서드를 뷰컨에 구현
    ▪️ C: 위 메서드를 DataSource class에 구현
  1. UITableViewDelegate
  • (필요하면) tableView(_,didSelectRowAt:) 등의 User Interaction 메서드
    ▪️ A: 위 메서드를 전부 override로 구현
    ▪️ B: 위 메서드를 뷰컨에 구현
    ▪️ C: 위 메서드를 Delegate class에 구현
  1. IBOutlet으로 연결한 table view의 dataSource/delegate 프로퍼티에 적절한 인스턴스를 할당
    ▪️ A: 불필요
    ▪️ B: 두 프로퍼티에 self를 할당
    ▪️ C: 두 프로퍼티에 각각 직접 만든 타입의 인스턴스를 전달
    (ex. tableView.delegate = CustomTableViewDelegate())

✔️ 주요 메서드 호출 시점

  • numberOfSections(in:)
    Table View가 처음 보여질 때. 최초 1회

  • tableView(_, numberOfRowsInSection:)
    Table View가 처음 보여질 때.
    시점 상으로는 최초 한 번만 호출되나 각 Section마다 호출된다
    즉, 처음 시점에 N번 호출되고 끝

  • tableView(_, cellForRowAt:)
    row가 화면에 보여질 때 및 row가 클릭될 때
    (1) 처음 Table view가 보여질 때 화면에 보여질 row에 대해 호출
    (2) 스크롤을 내리면서 새로이 보이는 row에 대해 호출
    (3) row를 클릭하면 호출


✔️ 기타 메서드 설명

  • cell.defaultContentConfigruation()
    cell의 '일부 프로퍼티'에 대한 시스템 default 값입니다
    여기서 일부 프로퍼티란, UITableViewCell이 기본적으로 가지고 있는 image/text/secondaryText 등을 말합니다

    ❗️주의1❗️
    일부 프로퍼티에 속하지 않는 backgroundColor 등은 되돌리지 않습니다
    그러므로 custom cell을 사용하는 경우 그닥.. 쓸모가 없는 듯 합니다

    ❗️주의2❗️
    시스템 default가 스토리보드 default를 뜻하는게 아닙니다. 시스템이 가진 default를 불러오는 메서드이므로 스토리보드에서 정적으로 label text에 어떤 값을 주더라도 이 메서드에는 영향이 없습니다

  • tableView.register(_:forCellReuseIdentifier:)
    '자체적으로 만든' cell class를 Table View에 등록하는 메서드입니다
    즉, forCellReuseIdentifier로 cell을 찾는다면 이 class 인스턴스로 만들어주면 된다고 Table View에게 알리는 것입니다

    ❗️주의❗️
    이 과정은 prototype cell을 직접 생성하는 경우 필요합니다
    즉, 스토리보드에서 table view에 prototype cell을 넣어줬다면 불필요합니다



🐼 Table View 다루기 종합

출처 : Filling a Table with Data

Table view의 cell을
(1) data source object를 사용하여 동적으로 생성/설정하거나
(2) 스토리보드를 통해 정적으로 제공한다

✔️ Overview

🔘 Data Source Object
Table view는 App의 인터페이스에서 data-driven적인 요소입니다

우리는 data source object를 사용하여(=UITableViewDataSource 프로토콜을 채택하는 object) App의 data를 제공하면서, data의 각 조각이 화면에 어떻게 표시될지도 제공합니다

table view는 화면에서 view들을 정렬하고 data를 최신으로 유지하기 위해 data source object와 작업합니다

🔘 row and section
table view는 data를 row와 section으로 조직합니다
row는 각 data item을 보여주고, section은 서로 관련된 row들을 그룹짓습니다

section은 필수적이진 않지만 이미 계층구조를 가지는 data를 나타낼 때는 좋은 방법입니다
예로, 연락처 App에서 각 row는 연락처의 이름을 표시하고 첫글자를 기준으로 section을 묶습니다


✔️ row와 section의 총 개수를 알려줘야 한다

table view는 화면에 나타나기 전에 당신에게 row와 section의 총 개수가 몇개인지를 명시하도록 요청합니다
당신의 Data source object는 아래 2개 메서드를 통해 이 정보를 제공합니다

func numberOfSections(in tableView: UITableView) -> Int  // Optional 
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int

우리는 이 메서드를 직접 구현하게 되는데 그 내용은 row와 section의 개수를 return하는 것입니다

그리고, 이 메서드들은 빈번히 호출되므로 내용이 가벼워야 합니다

🔘 Data 구조를 잘 선택할 것
Data 구조를 잘 선택하여 row/section 정보를 찾아내기 쉽게 하는 것이 좋습니다

예로, table의 data를 관리하기 위해 Array를 사용하는 것을 고려해볼 수 있습니다
Array는 table view의 자연스러운 구조와 일치하므로 row/section을 구조화하는 좋은 tool입니다

🔘 예제
아래 예제 코드는 section이 여러 개인 table에서 row/section 개수를 반환하는 Data source 메서드를 보여줍니다
각 row가 String을 보여주려 하고 2중 Array 구조로 구현하였습니다

var hierarchicalData = [[String]]() 
 
override func numberOfSections(in tableView: UITableView) -> Int {
   return hierarchicalData.count
}
   
override func tableView(_ tableView: UITableView, 
                        numberOfRowsInSection section: Int) -> Int {
   return hierarchicalData[section].count
}

✔️ 각 row Appearance 정의하기 (= Cell 정의하기)

(스토리보드에서 작업하는 경우)

🔘 Cell
스토리보드에서 cell을 사용하여 row의 외형을 정의합니다
cell은 UITableViewCell의 인스턴스로 row를 위한 템플릿처럼 동작합니다

🔘 Cell은 subview를 가질 수 있습니다
cell은 view이고 원한다면 subview를 가질 수 있습니다
label, image 등을 content area에 추가하고 constraint를 주어 정렬할 수 있습니다

🔘 Prototype Cell
table view를 App에 추가하면, 이 table view는 하나의 Prototype Cell을 포함하고 있습니다
prototype cell을 추가하려면 table view를 선택하고 'Prototype Cells attribute'를 업데이트하면 됩니다

🔘 Cell Style
각 cell은 외형을 정의하는 'style'을 가집니다
UIKit이 제공하는 표준 style 중 하나를 선택할 수도 있고 직접 만들 수도 있습니다

🔘 prototype cell 정의하기 예시
아래 그림은 2개의 prototype cell을 가진 table을 보여줍니다
(모두 표준 style을 사용합니다)

각 prototype cell을 위해, 스토리보드에서 아래의 action들을 수행해야 합니다

  • Style 설정 : custom 혹은 표준 style 중 하나로
  • Identifier 설정 : 추후 identifier를 기반으로 Cell을 찾아 사용
  • (custom일 경우) View/Constraint 설정
  • (custom일 경우) class 설정 : Identity inspector에 있음

🔘 custom view들이 포함된 cell
custom view들이 포함된 cell을 만든다면, 이 view들을 관리하기 위해 UITableViewCell를 상속하는 subclass를 정의해야 합니다
이 subclass에서 outlet을 추가하고 스토리보드의 custom view들과 연결합니다
이 outlet들을 런타임에 설정할 필요가 있을 것입니다?

추가로, cell 외형을 설정하는 방법에 대한 정보는 Configuring the Cells for Your Table


✔️ 각 row를 위한 cell을 생성/설정하기

위에서 만든 Cell을 (필요한 경우) 생성하고, 각 row에 적용해봅시다

table view는 onscreen 전에 data source object에게 '화면에 보이는 row와 그 근처 row'들에 설정할 cell을 요청합니다

data source object의 tableView(_:cellForRowAt:) 메서드가 이 요청을 처리합니다
(빈번히 호출되므로 내용이 가벼워야 합니다)

🔘 tableView(_:cellForRowAt:) 구현 방법

  1. cell object를 찾기 위해 table view의 dequeueReusableCell(withIdentifier:for:) 메서드 호출
  2. cell의 view를 설정 (ex. 해당 row의 title을 설정한다던지)
  3. cell을 table view에게 반환

🔘 style별 Cell의 View 설정
표준 style의 cell은, title같은 내부 view들을 자체적으로 프로퍼티로 가지고 있습니다
custom cell일 경우, 직접 view를 cell에 추가하고 outlet을 연결해야 합니다

🔘 예제
아래 예제는 textLabel 하나를 포함하는 Basic style cell을 설정하는 data source 메서드를 보여줍니다

Basic style cell의 경우, 자체적으로 가지고 있는 내부 view 프로퍼티 textLabel에 data를 설정해주면 됩니다

override func tableView(_ tableView: UITableView,
                        cellForRowAt indexPath: IndexPath) -> UITableViewCell {
   // Ask for a cell of the appropriate type.
   let cell = tableView.dequeueReusableCell(withIdentifier: "basicStyleCell", for: indexPath)
        
   // Configure the cell’s contents with the row and section number.
   // The Basic cell style guarantees a label view is present in textLabel.
   cell.textLabel!.text = "Row \(indexPath.row)"
   return cell
}

🔘 Cell은 lazy하게 생성된다
table view는 자신이 가진 모든 row에 대한 cell 생성을 요청하지 않습니다
대신, table의 보이는 부분 내부나 근처의 cell에 대해서만 요청합니다

이렇게 cell을 lazily 생성하는 것은 table이 사용하는 메모리를 줄입니다
하지만, data source object가 cell을 '빨리' 만들어야 합니다

그러므로, tableView(_:cellForRowAt:)가 data를 load하거나 오래 걸리는 작업을 수행하지 않게 해야 합니다


✔️ Performance를 위한 Data Prefetch

table view에서 스크롤 성능은 매우 중요합니다
만약 data를 fetch하는게 오래 걸리고 비싼 동작이라면(DB에서 가져와야 한다던지), prefetching data source object를 사용할 수 있습니다 (=UITableViewDataSourcePrefetching을 채택하는 객체)

이를 통해 스크롤하기 전에 비동기적으로 data를 가져오도록 할 수 있습니다

🔘 대략적인 구현방법
1. UITableViewDataSourcePrefetching 프로토콜을 채택하는 객체 생성
2. Table View의 prefetchDataSource 프로퍼티에 이를 할당
3. tableView(_:prefetchRowsAt:) 메서드에 오래걸리는 작업을 비동기로 시작하도록 구현
4. tableView(_:cellForRowAt:)에서 미리 가져온 data를 사용
5. (필요하면) tableView(_:cancelPrefetchingForRowsAt:)에서 data 로딩을 취소시킬 수도 있음

다만, Row를 보여줘야 하는 시점에 Data 로딩이 미처 완료되지 못할 수도 있으므로 이런 상황을 처리할 수 있도록 만들어야 합니다


✔️ Data를 스토리보드에서 설정하기

content가 바뀌지 않는다면 Table View가 동적으로 계속 불러오지 않고 스토리보드에 static하게 박아넣는 방법도 있습니다
모든 내용을 스토리보드에서 명시한다면 data source object를 구현할 필요가 없어집니다

런타임에 UIKit은 스토리보드로부터 data를 가져와서 관리합니다

하지만, 스토리보드로 static하게 박아넣은 data는 변경할 수 없으므로 신중하게 사용해야 합니다



🐭 Cell 디자인

출처 : Configuring the Cells for Your Table

table row의 content와 외형을 명시하는 방법
(스토리보드 버전)

✔️ Overview

Cell은 row의 시각적인 표현을 제공합니다

대부분의 경우 서로 다른 타입의 cell을 한 두개 정도만 제공하게 됩니다

가장 중요한 정보가 눈에 띄도록 view와 view설정을 신중하게 선택하여 cell을 디자인해야 합니다

cell 외형을 스토리보드에서 정의하게 됩니다

Xcode는 각 table 당 1개의 prototype cell을 기본으로 제공하며 필요에 따라 추가할 수 있습니다

prototype cell은 cell 외형에 대한 템플릿 역할을 하여 row에 나타낼 view와 arrangement가 포함됩니다

런타임에, table의 data source object가 prototype을 참고하여 실제 cell을 생성하게 되며 App의 data로 이것들의 내용을 설정합니다


✔️ Cell Reuse identifier

Reuse identifier는 각 prototype cell에 할당하는 string 값으로, cell 생성/재사용을 가능하게 만들어 줍니다
스토리보드에서 각 prototype cell에 유일한 id를 할당하게 됩니다

🔘 dequeueReusableCell()
런타임에 cell 객체가 필요한 순간에, table view의 dequeueReusableCell(withIdentifier:for:) 메서드에 원하는 id를 전달하여 호출하면 됩니다
table view는 이미 생성된 cell을 보관하는 내부적인 queue를 가지고 있어 요청받은 cell이 queue에 들어있다면 그 cell을 반환하게 됩니다
반면, queue에 없다면 스토리보드에 정의된 prototype cell을 가지고 새로운 cell을 생성하게 됩니다
cell을 재사용하는 것은 스크롤하는 등의 중요한 순간에 메모리 할당을 최소화하여 성능을 향상시키게 됩니다


✔️ 표준 style Cell

cell을 설정하는 가장 간단한 방법은 UITableViewCell에 의해 제공되는 표준 sytle을 사용하는 것입니다 (표준 style은 바로 사용할 수 있습니다)

표준 style cell에 대해서는 별도로 custom subclass를 제공할 필요가 없습니다
각 style은 1,2개의 Label과 Image를 포함하는데 style별로 이 content들의 위치가 다르게 배치됩니다

🔘 표준 style로 설정
표준 style 중 하나로 prototype cell을 설정하려면 스토리보드에서 Style 프로퍼티를 적절히 설정해주면 됩니다

🔘 Cell Content 설정
코드에서 cell의 content를 설정하려면, tableView(_:cellForRowAt:) 메서드에서 textLabel, detailTextLabel, imageView 프로퍼티를 조절해주면 됩니다

style에 따라 이 프로퍼티들을 전부 지원하지는 않을 수 있습니다.
예로, Basic style은 detailTextLabel을 지원하지 않습니다

🔘 예제 코드

override func tableView(_ tableView: UITableView, 
             cellForRowAt indexPath: IndexPath) -> UITableViewCell {
   // Reuse or create a cell. 
   let cell = tableView.dequeueReusableCell(withIdentifier: "basicStyle", for: indexPath)

   // For a standard cell, use the UITableViewCell properties.
   cell.textLabel!.text = "Title text"
   cell.imageView!.image = UIImage(named: "bunny")
   return cell
}

✔️ Custom style Cell

표준 style이 마음에 안들면 직접 custom할 수 있습니다
대신 내부 content에 대한 view/configuration/size/position 등을 명시해야 합니다
Label과 Image같은 Static view들이 가장 좋은 content를 만듭니다?

🔘 User Interaction은 지양할 것
content view에 User Interaction 요소들을 넣는걸 피해야 합니다
(ex. scroll/table/collection 등)

🔘 Stack View는 item을 최소화
content로 stack view를 추가할 수도 있습니다
다만, 성능 이슈가 발생할 수 있으므로 item 개수는 최소화하는게 좋습니다

🔘 Constraint 설정
custom cell을 설정하기 위해 스토리보드에서 view를 드래그하여 prototype cell에 놓습니다
cell 내부 content area에서 view의 position을 잡기위해 constraint를 적용할 수 있습니다

참고로, constraint를 적용할 때, Constraint to margins를 체크하는게 좋습니다

🔘 UITableViewCell class 정의
custom cell을 위한 UITableViewCell의 subclass를 정의해야 합니다
이 subclass에 IBOutlet을 연결하여 content를 제어합니다

class FoodCell: UITableViewCell {
    @IBOutlet var name : UILabel?
}

class MyTable: TableViewController {
  override func tableView(_ tableView: UITableView, 
               cellForRowAt indexPath: IndexPath) -> UITableViewCell {

     // Reuse or create a cell of the appropriate type.
     let cell = tableView.dequeueReusableCell(withIdentifier: "foodCellType", 
                           for: indexPath) as! FoodCell
     ...

✔️ Row의 Height 변경하기

Table View는 row들의 높이를 Cell과 별개로 관리합니다

🔘 Default 높이값
UITableView는 row를 위해 기본적으로 default 높이값을 제공하지만 rowHeight라는 프로퍼티를 조절하여 이 default값을 바꿀 수 있습니다

즉, rowHeight 프로퍼티는 모든 row가 같은 높이를 가지고자 할 때 사용합니다

이렇게 설정하는게 delegate를 사용하여 변경하는 것보다 효율적입니다


🔘 Dynamic 높이값
만약 모든 row의 높이가 같지 않고자 할 때는 delegate의 tableView(_:heightForRowAt:) 메서드를 통해 동적으로 설정할 수 있습니다
이 메서드를 구현할 때, 각 row에 대한 높이값을 제공해야 합니다

override func tableView(_ tableView: UITableView, 
           heightForRowAt indexPath: IndexPath) -> CGFloat {
   // Make the first row larger to accommodate a custom cell.
  if indexPath.row == 0 {
      return 80
   }

   // Use the default size for all other rows.
   return UITableView.automaticDimension
}

이 메서드 또한 visible row에 대해서만 호출됩니다


✔️ 재사용되기 전에 복구시키기

Cell을 재사용하기 전에 원래대로 복구시키는 것이 필요할 수 있습니다
예로, 특정 row를 위해 빨강색으로 변경하였는데 이것이 다른 row를 위해 재사용될 때 특별한 조치를 하지 않으면 빨강색인 그대로 재사용됩니다

🔘 prepareForReuse()
UITableViewCell의 prepareForReuse() 메서드를 통해 복구작업을 구현할 수 있습니다
복구작업이므로 Cell을 Reuse하는 경우에 호출되고 Cell을 새로 생성하는 경우에는 호출되지 않습니다

prepareForReuse()의 호출 시점은 dequeueReusableCell()에서 cell을 return하기 직전입니다

이후에 본격적으로 Cell 설정을 하는 tableView(_:cellForRowAt:)가 호출될 예정이므로 cellForRowAt에서 설정하지 않는 것만 복구해주면 됩니다


✔️ Accessory View 추가하기

선택적으로 Accessory view를 추가할 수 있습니다
이는 시스템이 제공하는 view로, cell의 trailing edge에 나타납니다

🔘 목적
Accessory view를 통해 유저에게 표준 cell 동작을 제공할 수 있습니다
예로, row를 탭하면 detail 정보를 띄워주는 detail button을 추가하는게 있습니다

🔘 Accessory view 추가하는 법

  • 스토리보드
    Cell의 Accessory attribute에서 선택합니다

  • 코드
    Cell의 accessoryType 프로퍼티를 변경합니다

🔘 유저들의 기대
유저는 Accessory view를 탭했을 때 '어떤 동작이 발생하기를' 기대합니다


🐸 Table View Performance

profile
노션으로 이사갑니다 https://tungsten-run-778.notion.site/Study-Archive-98e51c3793684d428070695d5722d1fe

0개의 댓글