티스토리 뷰

iOS

[iOS] UITableView cell LifeCycle

Peppo 2024. 12. 22. 00:56
728x90

최근 UIKit을 통해 작업할 일이 생겼는데

생각보다 TableView의 활용도가 꽤 많다는걸 느꼈습니다.

 

오늘은 TableView를 사용한다면 알아야할 것중 하나인 lifeCycle에 대해 블로깅을 해보려고 합니다.

 


 

아래 예시의 UI는 code base로 구현합니다.

 

테스트 해볼 UI는 아래와 같아요

 

 

TableView

 

가장 기본적인 TableView 형태로 만들고나서,

tableView의 delegate, dataSource 프로토콜에서 lifeCycle 역할을 하는 메서드들을 나열해보자면 아래와 같습니다.

 

UITableViewDelegate

 

willDisplay

 

cell이 나타날때 쯤 호출

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath)

 

: 테이블 뷰가 특정 행의 셀을 그리려고 할때 delegate에 알립니다.

 

활용 예시

: 무한 스크롤, 로딩 인디케이터 추가 시

 

 

didEndDisplaying

 

cell이 사라진 후 호출

func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath)

 

: delegate에게 지정된 셀이 테이블에서 제거됐을 경우 알립니다.

 

활용 예시

: 이미지 다운로드 취소

  - cell에 이미지가 있는 경우, 화면에 보여질 cell이 이미지 다운로드를 시작하게 되는데 스크롤을 빠르게하게 되면 사라질 cell은 이미지 다운로드를 취소하도록 구현

 

 

 

UITableViewDataSource

 

cellForRowAt

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell

 

: tableView의 특정 위치에 삽입할 cell의 dataSource를 요청 합니다.

여기서 dataSource를 요청 한다는 말이 아래 영상을 보면 이해가 되실겁니다.

 

row값이 15가 보일때 호출되는 indexPath의 값은 [0, 16] 인걸 보면 알 수 있듯

다음 dataSource를 요청한다는걸 알 수 있습니다. 

참고
indexPath의 0번째 index는 section
indexPath의 1번빼 index는 row 입니다.
 

 

 
 
Cell

 

init

 

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?)

 

cell을 초기화 하는 부분 입니다. 

tableView, collectionView의 경우 

일정 부분 cell을 초기화를 한 후 나머지 스크롤로 생성되는 cell들은 이전에 없어진 cell들을 queue에 담아두고 재사용 합니다.

 

이번에도 아래 영상을 보면 알 수 있듯 

일정부분까지만 cell 초기화를 하고 추가로 생성되는 cell은 재사용이 되기때문에 cell init이 호출되지 않는걸 볼 수 있습니다.

 

 

 

prepareForReuse

 

override func prepareForReuse()

 

cell init에서 언급했던 재사용 부분 입니다. 

처음 cell init이 됐을때는 호출되지 않다가 init 이후 재사용 되는 부분에서는 prepareForReuse 메서드가 호출됩니다.

 

활용 예시

 

  - UI 초기화  

 

 

정리를 해보다가 궁금한점이 생겼는데 

willDisplay, prepareReuse가 호출되는 시점은 비슷한데 어떤경우에 따라 사용해야 하는지 찾아봤습니다.

 

willDisplay와 prepareReuse의 차이점?

테스트 해보면서 느꼈던 점을 정리해보자면 아래 표와 같았습니다

  willDisplay prepareForReuse
호출 시점 cell이 화면에 표시되기 직전 cell이 재사용되기 직전
사용 목적 cell의 최종 상태 업데이트 cell의 이전 상태 초기화
대상 cell 화면에 보이는 모든 cell 재사용되는 cell
주요 작업 데이터 바인딩, UI 업데이트 데이터 초기화

 

 

 

마무리해보자면 TableView LifeCycle은 아래와 같습니다.

 

 

순서

  1. cell init
  2. cellForRowAt
  3. (cell이 없어질때) didEndDisplaying cell
  4. willDisplay cell
  5. ============ cell init 이후 ==========
  6. prepareForReuese
  7. cellForRowAt
  8. (cell이 없어질때) didEndDisplaying cell
  9. willDisplay cell

 

 


 

UIKit에 대해 많이 접해보지 못해 기본적인 부분을 놓치고 있었는데

이번기회에 메서드별로 테스트도 해보니 어느정도 정리가 되는것 같았습니다.

 

CollectionView도 비슷한 lifeCycle로 알고 있는데 추후 사용해보고 또 느낀점을 포스팅해봐야겠네요.

 

아래는 테스트했던 전체 코드 입니다.

 

⬇️ 전체 코드 ⬇️

더보기

 

import UIKit

class ViewController: UIViewController {
    
    private lazy var tableView = {
        let t = UITableView()
        t.translatesAutoresizingMaskIntoConstraints = false
        t.rowHeight = 56
        t.delegate = self
        t.dataSource = self
        t.register(SomeCell.self, forCellReuseIdentifier: SomeCell.Constants.identifier)
        return t
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.setupUI()
        self.setupLayout()
    }

    private func setupUI() {

    }
    
    private func setupLayout() {
        self.view.addSubview(self.tableView)
        
        NSLayoutConstraint.activate([
            self.tableView.topAnchor.constraint(equalTo: self.view.topAnchor),
            self.tableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
            self.tableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
            self.tableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
        ])
    }

}

extension ViewController: UITableViewDelegate {
    
    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        print("========= willDisplay index: \(indexPath.row)")
    }
    
    func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        print("========= didEndDisplaying index: \(indexPath.row)")
    }
    
}

extension ViewController: UITableViewDataSource {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 30
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell: UITableViewCell
        
        guard let someCell: SomeCell = tableView.dequeueReusableCell(
            withIdentifier: SomeCell.Constants.identifier,
            for: indexPath
        ) as? SomeCell
        else {
            return UITableViewCell()
        }
        
        print("========= cellForRowAt index: \(indexPath)")
        someCell.setupUI(indexPathRow: indexPath.row)
        cell = someCell
        return cell
    }
    
    
}


final class SomeCell: UITableViewCell {
    
    private lazy var label: UILabel = {
        let v = UILabel()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.font = .boldSystemFont(ofSize: 24)
        v.textColor = .black
        return v
    }()
    
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        print("========= Cell init")
        setupLayout()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func prepareForReuse() {
        super.prepareForReuse()
        print("========= \(#function)")
    }
    
    func setupUI(indexPathRow: Int) {
        self.label.text = "label row index: \(indexPathRow)"
    }
    
    private func setupLayout() {
        self.contentView.addSubview(self.label)
        
        NSLayoutConstraint.activate([
            self.label.centerXAnchor.constraint(equalTo: self.contentView.centerXAnchor),
            self.label.centerYAnchor.constraint(equalTo: self.contentView.centerYAnchor)
        ])
    }
    
}

extension SomeCell {
    
    enum Constants {
        static let identifier: String = "SomeCell"
    }

}

 

 

728x90