2025.03.15 - [IT/프로그래밍 노트] - 027 Table view , Delegate Pattern
027 Table view , Delegate Pattern
자동목차iOS 버전 별 사용자 비율 xcode - General - MinimumDeployments - 개발하는 앱의 최소 소프트웨어 버전을 설정하는 메뉴다. https://developer.apple.com/support/app-store/ App Store - Support - Apple DeveloperApp St
margot33-0.tistory.com
DataSource
델리게이트 패턴에서는 메서드가 언제 호출되고 몇 번 호출되는지, 어떤 순서로 호출되는지 파악하는게 정말 중요하다
새로운 걸 공부할 때 마다 매번 확인해보는게 좋다. developer.apple.com 에서 UITableViewDataSource를 보면 다 나온다.
class ViewController: UIViewController, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("#1", #function)
return 100
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("#2", #function,indexPath)
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "\(indexPath.section) - \(indexPath.row)"
return cell
}
값을 리턴하기 전에 print("#1", #function) 메서드를 출력해서 언제 호출되는지 알아보았다.
print(#function) 이라고 하면 프린트가 호출된 메서드의 이름을 보여준다. 알아보기 쉽게 쉽게 1번 메서드라는 표시로 "#1" 넣었음
CellForRowAt 메서드는 #2 로 구분하기 쉽게 표시, 메서드 이름과 파라미터로 전달된 indexPath까지 print 되도록 설정했다.
시뮬레이터 실행하고 나면 콘솔창에 해당 메서드가 실행되는 방식이 보인다.
실제 화면에 표시되는 창보다 1,2개정도 더 메서드를 실행한다고 한다.
그래서 화면에는 0-17 까지 보이는데 콘솔창에서 볼 수 있는 메서드는 0,18 까지 실행되었다.
화면을 스크롤 하게 되면 다시 2번 메서드가 실행되어 18 이후의 숫자도 표시된다.
계속해서 아래로 스크롤 하게 되면 19,20,21,22 ... 이렇게 실행된다.
아래로 스크롤 하게 되면 0,1,2 같은 숫자들은 화면에서 사라지게 되는데
화면을 위로 스크롤 할때에는 재사용큐에 들어가있던 메서드가 호출되어 재사용, 다시 3,2,1 과 같은 형식으로 데이터를 불러온다.
재사용큐
dequeueReusableCell(withIdentifier:for:) 메서드
재사용 가능한 셀을 큐에서 꺼내와서 반환하거나 새로운 셀을 생성해 반환하는 역할을 가진 메서드다.
테이블 뷰는 표시할 셀을 모두 만드는 것이 아니라 화면에 표시할 수 있는 갯수만큼 만들어 놓고 여유로 1,2 개 정도 더 만들어 놓는다.
이때 화면에 보이지 않는 셀은 재사용큐에 보관되고 스크롤과 동시에 계속 재사용 하는 방식으로 동작한다.
적은 메모리로 아주 많은 데이터를 표시할 수 있고 재사용하기 때문에 코드실행에 필요한 시간도 짧아진다.
스크롤 성능이 높아져서 부드럽게 스크롤 되는 결과를 가져온다.
withIdentifier: 에는 cell의 identifier 값이 오타없이 정확하게 들어가야한다.
실제로 재사용된다는 증거를 확인해보자.
class ViewController: UIViewController, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("#1", #function)
return 100
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("#2", #function,indexPath)
// #1
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
// #2
if indexPath.row < 19 {
cell.textLabel?.text = "\(indexPath.section) - \(indexPath.row)"
}
// #3
return cell
}
18까지는 잘 출력되던 숫자가 19부터는 타이틀 이라는 글자가 출력되기 시작했다.
저 타이틀은 스토리 보드에서 만들었던 Table view cell을 추가했을 때 기본으로 입력되어 있던 문자열이다.
그럼 저 title이라는 건 재사용하지 않고 그대로 리턴해서 생긴 것이다.
다시 0부터 인덱스가 시작되는데 재사용큐에 들어가있던 첫번째 셀을 재사용한 것이다.
아까 19번째 셀을 재사용한 것이다.
위아래로 스크롤을 반복하다 보면 title이라는 것도 점점 사라지기 시작한다. (읭?)
어떤 셀이 어떤 인덱스에서 재사용되는지는 알 필요가 없고 셀이 재사용된다는 것과 2번 메서드는 특정 위치에 셀이 표시되기 직전에 반복적으로 호출 , 재사용된다라는 개념이 중요하다고 한다.
코드 블록 안의 2번 메서드는 스크롤 과 연관있는 메서드 이기 때문에 최대한 간결하게 코드를 작성해야 빠른 스크롤이 가능하다.
챌린지
좋아하는 과일 이름을 배열에 저장하고 속성으로 추가
배열에 저장되어 있는 과일 이름을 테이블 뷰에 표시
내 답변
class ViewController: UIViewController, UITableViewDataSource {
let fruit = ["포도" , "배" , "딸기", "사과", "바나나", "오렌지", "블루베리", "귤"]
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("#1", #function)
return fruit.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("#2", #function,indexPath)
// #1
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = fruit[indexPath.row]
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
흔히 하는 실수 cellForRowAt 메서드 안에서 for 반복문 쓰면 안된다.
이미 반복적으로 실행되는 메서드 이기 때문에 for 반복문을 쓸 필요가 없다.
코드 개선
DataSource나 Delegate는 프로토콜로 분류되어 있어 분리하기 좋다.
익스텐션은 타입을 확장할 때 쓴다던지 프로토콜 구현을 추가할 때도 사용가능 하다.
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("#1", #function)
return fruits.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("#2", #function,indexPath)
// #1
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = fruits[indexPath.row]
return cell
}
}
이런식으로 익스텐션으로 데이터 소스와 델리게이트를 분리 했다. 다른 파일로 분리해도 된다.
Delegate
검색
테이블 뷰에게 선택이벤트가 발생했을때 Delegate를 이용해 동작한다.
UITableViewDelegate 검색, developer.apple.com에서 Delegate 프로토콜에 대한 정보를 확인 가능하다.
첫번째는 아이콘📄은 아티클을 뜻하고 두번째 아이콘 ({})는 샘플코드(코드를 직접 다운로드 할 수 있음)를 뜻한다. 같이 공부하면 좋다고 한다.
나머지는 메서드 목록
메서드에는 will , did 가 포함되어 있는 경우가 상당히 많다.
will select row at 은 테이블 뷰가 터치를 감지한 직후에 호출되는 메서드 바로 이어서 didSelectRowAt이 호출된다
willDeselectRowAt은 선택이 해제되기 직전에 호출 didDeselectRowAt은 선택이 해제된 다음에 호출
TableView를 첫번째 파라미터로 전달된다는 공통점이 있고
indexPath를 통해서 사용자가 몇 번째 셀을 선택했는지 알 수 있다.
tableView(_:didSelectRowAt:) 메서드
리턴형이 없다.
별도로 리턴할 필요는 없다.
메서드 이름이 tableView라고 입력하면 자동 완성 된다.
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(fruits[indexPath.row])
}
}
익스텐션을 이용해서 UITableViewDelegate 프로토콜에서 didSelectRowAt 메서드의 기능을 추가하였다.
이제 시뮬레이터에서 각 항목을 누르면 거기에 해당하는 fruits의 인덱스에서 결과를 출력해줄 것이다.
섹션 추가
과정
추가할 배열들을 class 안에 배열 타입 추가
class ViewController: UIViewController {
let fruits = ["Apple" , "Banana" , "Melon"]
let languages = ["Swift", "Objective-C", "Java", "HTML", "CSS"]
}
UITableViewDataSource 검색
Discussion - 메서드를 구현하지 않으면 테이블뷰가 하나의 섹션으로 나타난다.
그러니 이 메서드를 통해서 섹션을 분류해줄 수 있다는 뜻이다.
UITableViewDataSource 의 numberOfSections(in:)을 통해 섹션을 추가한다.
func numberOfSections(in tableView: UITableView) -> Int {
print("#3", #function)
return 2
}
이 코드를 ViewController: UITableDataSource 익스텐션 안에 추가 하면 섹션의 수는 2개가 된다.
위 코드만 추가하면 이렇게 된다.
이유는 indexPath를 파라미터로 받고 있는데
섹션이 1개일 때 설정했던 코드 때문에 fruits의 갯수와 내용만 전달되고 있기 때문이다.
UITableDataSource의 numberOfRowsInSection 과 cellForRowAt 메서드를 수정해주면 된다. switch 문으로 나눠줄 수 있다.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("#1", #function, section)
switch section {
case 0:
return fruits.count
case 1:
return languages.count
default:
return 0
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("#2", #function,indexPath)
// #1
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
//#2
switch indexPath.section {
case 0:
cell.textLabel?.text = fruits[indexPath.row]
case 1:
cell.textLabel?.text = languages[indexPath.row]
default:
break
}
//#3
return cell
}
이렇게 수정해주면
이렇게 제대로 표시되게 된다.
header 와 footer
테이블 뷰에서는 섹션마다 헤더와 푸터를 추가할 수 있다.
UITableViewDelegate 에서 viewForHeaderInSection 메서드로 헤더를 디자인할 수 있고
UITableViewDataSource 에서 titleForHeaderInSection 메서드로 헤더의 내용을 넣을 수 있다.
developer.apple.com 에서 프로토콜을 검색해서 찾을 수 있는 메서드들이다.
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
switch section {
case 0:
return "좋아하는 과일"
case 1:
return "사용할 수 있는 언어"
default:
return nil
}
}
헤더를 넣는 코드는 switch 문을 이용해서 넣는 방법이다.
이걸로 각 섹션 별 Header , Footer 등등 넣을 수 있다.
UITableViewDelegate 에서도 didSelectRowAt 메서드에 switch문을 이용하여 각 섹션별에 맞게 알맞은 값을 출력할 수 있도록 설정할 수 있다.
위 코드로 Header 설정한 결과물
테이블 뷰의 스타일 바꾸기
스토리보드에서 Table view 선택 attribute inspector 에서 Table view - style - plain, grouped, inset Grouped 선택할 수 있다.
inset Grouped style로 적용한 경우 아래와 같이 바뀐다.
여백의 여유가 없다면
스토리 보드에서 Table view 선택 size inspector 에서 Sections 값을 지워 automatic으로 변경해주면
자동으로 여백을 판단해서 더 여유 있게 보여주게 된다.
cell의 디자인을 커스텀 하고 싶다면 plain 스타일로 하는게 여백 설정 등 용이하고 좋다고 한다.
'IT > 프로그래밍 노트' 카테고리의 다른 글
009 Tuple 튜플 (0) | 2025.03.17 |
---|---|
008 함수 function (0) | 2025.03.17 |
007 옵셔널 바인딩, nil coalescing operator, IUO (0) | 2025.03.15 |
027 Table view , Delegate Pattern (0) | 2025.03.15 |
006 while문, Optional 옵셔널 타입 (0) | 2025.03.12 |