앱 아이콘을 클릭하거나 혹은 다른 경로로 앱을 실행시켜보자. Application이 Create되고, OS가 앱을 Running State로 올려준다. 여기까지가 OS의 일이다. Running State가 되면 뷰의 라이프사이클이 시작된다.
개발자들은 OS가 하는 일(Application Create → Running State)에 권한은 없으나 이 과정에서 ‘이러한 이벤트가 있어!’ 하고 알림을 받을 수 있다.
'applicationDidEnterBackground됐어. 너 뭐 할래?' 같은 메소드가 정의되어 있고,
'ㅇㅇ 로그 찍을래.' 처럼 필요시 그에 따른 할 일을 처리하는 것이 Delegate에서 일어난다.
ios의 멀티윈도우를 위해 생긴 개념이다. ios13부터 추가됨!
SceneDelegate에서 윈도우가 두개 이상이 되었을 때의 이벤트를 받을 수 있다.
init
→ viewDidLoad
→ viewWillAppear
→ viewDidAppear
→ viewWillDisappear
→ viewDidDisappear
→ deinit
init
부터 viewWillAppear
, viewDidAppear
까지는 inflate 과정이다.
inflate는 iOS의 스토리보드/SwiftUI (Android에서의 xml 레이아웃/ComposeUI)를 OS의 메모리에 올리는 작업(= 컴퓨터가 알아들을 수 있게 이진수로 바꾸는 작업)을 말한다.
각각의 단계에서 무슨 일을 주로 할까?
TableView를 만들기 위해서는 DataSource와 Delegate가 필수 요소이다.
TableView DataSource
사용자에게 보여줄 데이터 관리
테이블뷰가 화면에 보여지기 전에 데이터소스에게 '내가 몇개의 데이터를 보여주어야 하는지' 물어보고, 각 섹션의 로우에 해당하는 cell을 보내달라고 부탁하면 데이터소스가 cell을 보내준다.
✏️ 섹션 수 반환 메소드
구현 필수인 메소드는 아니고, 구현하지 않을 경우 기본값인 1을 반환한다.
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
✏️ 섹션별 로우 수 반환 메소드 (필수 구현!)
func tableView(_ tableView: UITableView, numbersOfRowsInSection section: Int) -> Int {
return 3
}
✏️ 인덱스(n번째 섹션의 n번째 로우)에 해당하는 cell 반환 메소드 (필수 구현!)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "section: \(indexPath.section), row: \(indexPath.row)"
return cell
}
✏️ 섹션별 헤더 반환 메소드
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "Section \(section) Header"
}
TableView Delegate
테이블뷰 관리, 사용자 이벤트 핸들링
사용자가 cell을 선택하거나 편집하려는 시도를 하면 delegate에게 어떻게 해야하는지 물어보고 delegate가 알려주는대로 처리하거나 delegate에게 처리를 맡긴다.
간단한 예제를 만들어 보자.
프로젝트를 생성하고, Cocoa Touch Class 새 파일을 만들 건데 UITableViewController를 상속받는 TableViewController라는 이름의 파일로 만들어준다.
Main.storyboard
스토리보드에 기본으로 생성되어 있는 View Controller를 삭제하고 오브젝트 라이브러리로부터 Table View Controller를 하나 생성한다.
해당 View Controller를 잡고 [Editor] > [Embed In] > [Navigation Controller]를 눌러 뷰 컨트롤러를 내비게이션 컨트롤러에 임베드해준다.
Navigation Controller에서 할 일
이 내비게이션 컨트롤러를 스토리보드의 첫번째 뷰 컨트롤러로 지정하기 위해 [Attribute Inspector]에서 [Is Initial View Controller]에 체크해주자.
View Controller에서 할 일
뷰 컨트롤러의 [Identify Inspector]에서 Class를 TableViewController.swift로 선택한다.
cell의 [Attribute Inspector]에서 Identifier를 MyCell이라는 임의의 이름으로 설정해준다.
이제 스토리보드에서 할 일은 끝났다. 아까 새로 만들었던 파일의 코드를 마저 작성해보자.
TableViewController.swift
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
/// 섹션 수 반환
override func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
/// 섹션별 로우 수 반환 (필수)
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
/// 인덱스에 해당하는 셀 반환 (필수)
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath)
cell.textLabel?.text = "section \(indexPath.section), row: \(indexPath.row)"
return cell
}
// 섹션별 헤더 반환
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "Section \(section) Header"
}
}
UITableViewController의 정의부를 보면
이렇게 테이블뷰를 만들기 위해 필요한 Delegate와 DataSource를 모두 상속받고 있다.
우리는 처음에 기본으로 만들어져 있던 ViewController를 지우고 UITableViewController를 상속받는 TableViewController를 새로 생성해주었기 때문에 코드상에서 상속부가 상당히 간단해졌다.
하지만 기본 ViewController를 그대로 사용했다면
class ViewController: UIViewController, UITableViewController, UITableViewDataSource { }
이런 식으로 불필요하게 상속부가 길어졌을 것이다.
cellForRowAt 파라미터를 받는 tableView 메소드를 보자. dequeueReusableCell
이라는 걸 사용하는데, 이게 뭘까?
테이블뷰는 테이블 높이와 셀 높이를 기반으로 셀 수를 생성한다. 만약 현재 화면에서 셀이 6개까지밖에 안 보인다면, 6개의 셀에 대한 메모리 할당만 이루어지는 것이다. 테이블뷰를 스크롤한다고 해도 동일한 셀이 사용되고 DataSource를 기반으로 셀 내용만 바뀌게 된다.
셀이 스크롤로 인해 화면 밖으로 밀려나면, 해당 셀은 reuse pool에 들어가게 되고, 우리가 dequeueReusableCell을 호출할 때 반환된다. 이런 식으로 메모리를 절약할 수 있는 것이다.
dequeueReusableCell의 파라미터인 withIdentifier
에는 우리가 재사용할 객체를 나타내는 문자열(Identifier)이 들어간다. 우리는 셀을 재사용할 것이고, 아까 스토리보드에서 셀의 identifier를 MyCell로 지정해주었으므로 이 파라미터의 값에 "MyCell"을 넘겨준 것이다.
또 다른 파라미터인 for
에는 셀의 위치를 지정하는 indexPath가 들어간다. indexPath는 [section, row] 로 이루어져 있어서 이 셀이 몇 번째 섹션의 몇 번째 로우에 위치하는지 알 수 있다.
시뮬레이터를 실행시켜보면 원하는 대로 잘 적용된 모습을 확인할 수 있다 😆