[iOS/Swift] 얼레벌레 TableView 개념 (계속 업데이트 예정)

seb Incode·2024년 10월 15일
0
post-thumbnail

0. 개요

다른 웬만한 뷰들은 한 번 하면 글쿠나ㅇㅇ 하고 넘어가지는데 얘는 어려워서 기록용으로 남깁니다. 계속 업데이트 예정입니다

1. TableView 간단하게 구현해보기

먼저 프로젝트 하나 생성합니다.
프로젝트 이름 절대절대절대 "Swift"로 하면 안됩니다. 결국 빌드 에러나서 나중에 프로젝트 이름 수정했습니다. Swift뿐만 아니라 framework 내에 있는 키워드로도 프로젝트 이름을 하면 안된다고 합니다 흑.

Command+Shift+L 키 누른 후, view controller 찾아서 드래그 합니다.


두 개의 controller를 만들었습니다. 굳이 이렇게 한 이유는 기록을 위해서 입니다. 아무튼 새로 생성한 view controller를 클릭 -> 우측에서 "is initial view controller"를 체크합니다.

왜죠

이렇게 해야 빌드 했을 때 첫 화면이 방금 생성한 view controller가 되기 때문입니다. 노란색 네모로 표시해둔 것 처럼 initial view controller 왼쪽 옆에는 화살표가 따라옵니다.

이제 생성한 view controller에 대한 Cocoa Touch Class 파일을 만들어야 합니다.

왜죠

이 파일을 만들고 이 파일과 새로 만든 view controller를 연결해줘야, 화면 UI 상호작용 및 동작을 제어할 수 있습니다. 화면을 제어하는 코드를 이 Cocoa Touch Class 파일에 코딩하는 거죠

Class 이름은 자유롭게 쓰고, 밑에 "Subclass of:"에선 UIViewController를 선택합니다.


생겼습니다.
저 파일에 간단한 제어 코드 하나를 썼습니다.
view의 배경화면을 갈색으로 지정했습니다. 그대로 빌드 해봅니다.

안되는데요; 갈색 어디갔죠

아래 작업을 하지 않았기 때문입니다. 생성한 MyTableViewController.swift 파일과 새로 만든 view Controller를 반드시 연결해줘야합니다. 아래처럼 view Controller 클릭 -> 우측 "Class" 란에서 MyTableViewController를 선택해줘야 합니다.

그 후 다시 빌드해보면 이번엔 정상적으로 갈색으로 보입니다.

그 다음으로 Table View를 화면에 드래그 해줍니다.
Command + Shift + L 누른 후 "Table View" 검색해서 화면 안에 드래그 해줍니다.

Table View의 크기를 화면 꽉 차게 해주고 싶습니다. 쥔공이니까요.

아래 캡쳐 화면처럼 초록색으로 표시한 부분을 누르고 -> 왼(leading), 위(top), 오(trailing), 아래(bottom)을 0으로 채우고 -> "Add 4 Constraints"를 클릭합니다.
0의 의미는 크기가 아닌, 부모 뷰(바탕화면이라 칩니다) 사이의 margin 값으로 보시면 됩니다. margin을 0으로 주었기 때문에 간격 없이 Table View가 화면에 꽉차게 되는 것입니다.

이 부분은 또 Auto Layout 관련 내용인데 조만간 열심히 공부해서 기록해야겠습니다.

꽉 한 Table View를 Ctrl+드래그 해서 MyTableViewController.swift 파일에 끌어옵니다. 이렇게 끌어오면 상수 하나가 뿅 생깁니다. 덕분에 이제 이 swift 코드에서 저 상수를 이용해서 화면 내의 Table View에 접근할 수 있습니다.

이제 Table View의 알맹이인 Table Cell를 화면 위에 있는 Table View 위에 얹습니다. 아까처럼 Command + Shift + L 입니다. 캡쳐 화면이 누락되었지만 이름은 "myCell"로 지정했습니다.

이제 저 Table View에 리스트 형식으로 데이터를 놓아보겠습니다.
그러기 위해선 UITableViewDelegateUITableViewDataSource 프로토콜을 사용해야 합니다. 근본적으로 왜 그래야하는지는 지금 당장 파진 않겠습니다. 그러면 한도 끝도 없고 진도가 안 나가기 때문입니다.
MyTableViewController 클래스에 상속 시켜도 되지만, 저는 가독성을 위해 extension을 활용하여 밑에다 따로 구분해놨습니다. 이건 본인 스타일이라서 원하는대롱

에러나는데요

에러를 읽어보니, "너 저 프로토콜 사용하고 싶어? 그럼 필수적으로 함수 구현해라" 라는 의미입니다. fix 버튼을 눌러봅니다.

오 메서드 두 개를 만들어줬습니다. 이 두개를 필수적으로 구현해야 한다고 합니다. 이 부분 또한 깊게 파지 않겠습니다. 일단 주석을 달아 제가 이해한 메서드 역할을 기록했습니다.

extension ViewController: UITableViewDelegate, UITableViewDataSource{
// 테이블뷰의 셀 개수를 정의하는 메서드
// 셀 개수를 리턴하도록 구현하면 된다.
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        <#code#>
    }
    // 테이블 뷰의 각 셀을 화면에 리턴하는 메서드
    // 셀 데이터 개수만큼 호출됨
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        <#code#>
    }
    
}

일단 아래와 같이 코드를 아주아주 간단하게 하드코딩 하듯이 적었습니다. 한 줄씩 기록해보면,

return 5
- 이 테이블 뷰는 5개의 셀을 가지고 있다는 뜻입니다. 즉 화면에 5줄의 내용이 생길 것 입니다.
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)
- 화면에 얹어져 있는 "myCell" 이름의 Table Cell과 연결되는 셀 타입의 상수를 생성합니다.
cell.textLabel?.text = "Hello TableVIew !"
- 이 상수를 이용하여 화면의 "myCell"의 text에 "Hello TableView !"를 대입합니다.
return cell
- 위의 코드로 잘 생성된 셀을 리턴합니다.

Hello TableView ! 안보이는데요

네 또 코드를 추가해줘야 합니다.

아래 두 개 코드를 추가해줘야 각 셀들이 보입니다. 이 역시 일단은 근본적인 이해는 하지 않고, 아 필요하구나 정도로 익히고 진도를 빼겠습니다.

 myTableView.delegate = self
 myTableView.dataSource = self

그럼 최종적으로 아래 코드가 만들어집니다. 이걸 다시 빌드해보겠습니다.

//
//  MyTableViewController.swift
//  Swift
//
//  Created by  on 2024/10/15.
//

import UIKit

class MyTableViewController: UIViewController {

    @IBOutlet var myTableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .brown
        myTableView.backgroundColor = .green
        myTableView.delegate = self
        myTableView.dataSource = self
        
    }
    
}

extension MyTableViewController: UITableViewDelegate, UITableViewDataSource {
    // 테이블뷰의 셀 개수를 정의하는 메서드
    // 언뜻보면 셀 개수만큼 호출되는 것처럼 보이지만, 셀 데이터 추가하기 전에 테이블 뷰 레이아웃을 잡기 위해, 셀 데이터 셋팅하기 전에 작업을 하면서 내부적으로 몇 번씩 호출될 수 있음
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 5

    }
    
    // 테이블 뷰의 각 셀을 리턴하는 메서드
    // 셀 데이터 개수 만큼 호출됨
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)
        cell.textLabel?.text = "Hello TableVIew !"
        return cell
    }
    
    
}


잘 보입니다.

//
//  MyTableViewController.swift
//  Swift
//
//  Created by  on 2024/10/15.
//

import UIKit

class MyTableViewController: UIViewController {

    @IBOutlet var myTableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .brown
        myTableView.backgroundColor = .green
        myTableView.delegate = self
        myTableView.dataSource = self
        
    }
    
}

extension MyTableViewController: UITableViewDelegate, UITableViewDataSource {
    // 테이블뷰의 셀 개수를 정의하는 메서드
    // 언뜻보면 셀 개수만큼 호출되는 것처럼 보이지만, 셀 데이터 추가하기 전에 테이블 뷰 레이아웃을 잡기 위해, 셀 데이터 셋팅하기 전에 작업을 하면서 내부적으로 몇 번씩 호출될 수 있음
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 5

    }

2. 디버깅 해서 약간 더 살펴보기

저 두 개의 tableView() 메서드가 핵심이었던 것 같습니다. 그런데 하다보니 저 두 메서드가 언제, 얼마나 호출되는지 궁금해서 살짝 코드를 변경해서 print()로 로그를 찍어봤습니다.

⬇️

오..
일단 cell을 리턴하는 2번째(편의상 그렇게 부르겠습니다.) tableView()는 0,1,2로 순차적으로 인덱스를 가지며 3번 호출됩니다. 코드 위쪽에 선언한 배열의 크기가 3이라 셀의 개수가 3개라서 그런 것 같습니다.

이해 가능. 그런데..

cell의 개수를 리턴하는 1번째 tableView()는 왜 3번이나 호출되는건지 모르겠습니다. 그래서 처음엔 이 메서드도 셀의 개수만큼 호출된다고 추측했습니다. 하지만..

💡 로그를 보면, numberOfRowsInSection(첫번째 tableView())이 3번 호출된 후에 cellForRowAt(두번째 tableView())이 호출되는데, 이건 셀 데이터가 추가되기 전에 항상 호출되는 것이 아닙니다. 이 동작은 테이블 뷰의 정상적인 작동 방식입니다.
테이블 뷰는 기본적으로 화면에 그려질 셀의 개수를 알아야 하고, 이를 위해 미리 numberOfRowsInSection(첫번째 tableView())을 여러 번 호출할 수 있습니다. 이 동작은 성능 최적화를 위해 이루어집니다.

저의 스승님이신 챗GPT 은사님께서 알려주셨습니다.

결론:

"셀의 개수를 리턴하는 메서드를 왜 몇 번씩 호출하냐"
"이게 다 최적화를 위한거고 정상이니까 그런 줄 알아라"

가 결론이라고 볼 수 있습니다.

최종 소스 코드

//
//  MyTableViewController.swift
//  Swift
//
//  Created by  on 2024/10/15.
//

import UIKit

class MyTableViewController: UIViewController {
    
    let cellDatas = ["Hello TableVIew !", "This is TableView", "Welcome~"]

    @IBOutlet var myTableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .brown
        myTableView.backgroundColor = .green
        
        myTableView.delegate = self
        myTableView.dataSource = self
    }
    

}

extension MyTableViewController: UITableViewDelegate, UITableViewDataSource {
    // 테이블뷰의 셀 개수를 정의하는 메서드
    // 언뜻보면 셀 개수만큼 호출되는 것처럼 보이지만, 셀 데이터 추가하기 전에 테이블 뷰 레이아웃을 잡기 위해, 셀 데이터 셋팅하기 전에 작업을 하면서 내부적으로 몇 번씩 호출될 수 있음
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//        return 5
        print("tableView log!!!!!! \(section), \(cellDatas.count)")
        return cellDatas.count
    }
    
    // 테이블 뷰의 각 셀을 리턴하는 메서드
    // 셀 데이터 개수 만큼 호출됨
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)
//        cell.textLabel?.text = "Hello TableVIew !"
        cell.textLabel?.text = cellDatas[indexPath.row]
        print("log !!!!!! \(cellDatas[indexPath.row]) - \(indexPath.row)")
        return cell
    }
    
    
}

0개의 댓글