UITableView
SwiftUI List.
데이터를 목록 형태로 보여 줄 수 있는 UIComponent.
Section 을 이용해 그룹화 가능.
Section 의 Header / Footer 를 활용해 View 를 구성하여 추가적인 정보를 표시할 수 있다.
UITableViewDataResource & UITableViewDelegate
1. UITableViewDataResource : Data 를 받아 View 를 그려주는 역할. Delegate 에 의존하여 View 를 업데이트.
e.g. 총 섹션이 몇개인지, 섹션의 행은 몇개인지, 행에 어떤 정보를 표시할 것인지
Table View 를 생성하고 수정하는데 필요한 정보를 Table View 객체에 제공.
필연적으로 구현할 두 메서드.
1) 각 Section 에 표시할 Row 의 갯수를 나타내는 numberOfRowsInSection 메서드
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.tasks.count
}
tasks 의 배열의 count 를 반환한다.
2) 특정 Row 를 그리기 위해 필요한 Cell 을 반환하는 cellForRowAt 메서드
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let task = self.tasks[indexPath.row]
cell.textLabel?.text = task.title
// checkmark
if task.done {
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
return cell
}
스토리보드에서 정의한 cell 을 withIdentifier 를 통해 dequeueReusableCell 이라는 메서드를 이용해 가져온다.
## 왜 Queue 를 활용할까? - 불필요한 메모리 사용을 방지하기 위해
1000개의 Cell을 한번에 메모리에 넣지 않고, 다섯 개의 Cell 만을 넣고 Queue 의 특징을 활용해 1000개를 필요한 경우 보인다.
그렇다면 dequeueReusableCell 메서드란?
지정된 재사용 식별자(withIdentifier) 에 대한 재사용 가능한 테이블 뷰 셀 객체를 반환하고, 이를 테이블 뷰에 추가하는 메서드.
쉽게 말하면 Queue 를 활용해 Cell 을 재사용하는 것
indexPath : TableView 에서 Cell 의 위치를 반환한다.
이제 선언한 cell 에 데이터를 넣어주기 위해 tasks 배열에 저장된 task 를 가져온 후, cell 에 넘겨준다.
2. UITableViewDelegate : tableView 의 동작과 외관을 담당
e.g. 행의 높이, 행을 선택하면 어떤 액션을 취할 것인지
TableView 의 시각적인 부분을 설정하고, 행의 액션 관리, 엑세서리 뷰 지원 그리고 TableView 의 개별 행 편집을 도와준다.
1) Cell을 선택하였을 때 어떤 Cell 이 선택되었는지 알려주는 메서드
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
var task = self.tasks[indexPath.row]
task.done = !task.done
self.tasks[indexPath.row] = task
// 선택된 cell 만 reload 하게 구현
// 단일 행 뿐만 아니라 여러개의 특정 행을 업데이트 할 수 있다.
self.tableView.reloadRows(at: [indexPath], with: .automatic)
}
indexPath 인자를 통해 선택된 Cell 을 반환할 수 있으며,
reloadRows 메서드를 통해 단일 행 / 여러 개의 특정 행을 업데이트 할 수 있다.
2) Editing 메서드
@IBAction func tapEditButton(_ sender: UIBarButtonItem) {
// 앱 만들시 고려해야할 부분: tableView 에 아무 데이터가 없을 때는 Edit 버튼을 어떻게 처리해야 할까?
guard !self.tasks.isEmpty else { return }
self.navigationItem.leftBarButtonItem = self.doneButton
self.tableView.setEditing(true, animated: true)
}
Edit 버튼을 눌러서 아래와 같이 setEditing 메서드가 실행되었을 때, UITableViewDelegate 프로토콜을 준수한 ViewController 에서 어떤 메서드를 통해 기능을 구현할 수 있을까?
① 셀 삭제
commit edittingStyle 메서드를 통해 구현할 수 있다. Edit 모드에서 삭제 버튼을 눌렀을 때 삭제 버튼을 누른 셀이 어떤 셀인지 알려주는 메서드이다.
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
self.tasks.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
// 모든 셀이 삭제되면
if self.tasks.isEmpty {
self.tapDoneButton()
}
}
task 가 저장된 tasks 배열에서도, tableView 에서도 선택된 셀을 나타내는 indexPath 를 통해 제거한다.
② 셀 순서 변경
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
// task가 재정렬 되면 배열 자체도 재정렬 되어야 하므로 tasks 라는 인스턴스를 따로 생성하여 배열을 받는다
var tasks = self.tasks
let task = tasks[sourceIndexPath.row]
tasks.remove(at: sourceIndexPath.row)
tasks.insert(task, at: destinationIndexPath.row)
self.tasks = tasks
}
sourceIndexPath 와 destinationIndexPath 두 인자를 통해 순서를 변경할 수 있다.
Task 추가 버튼(Alert) & Edit 버튼
1. Task 추가 버튼(Alert)
추가 버튼을 눌렀을 때 다음과 같은 Alert 를 발생시키도록 구현한다.
먼저 추가 버튼을 눌렀을 때에 대한 @IBAction 메서드를 구현한다.
@IBAction func tapAddButton(_ sender: UIBarButtonItem) {
let alert = UIAlertController(title: "할 일 등록", message: nil, preferredStyle: .alert)
let registerButton = UIAlertAction(title: "등록", style: .default, handler: { [weak self] _ in
guard let title = alert.textFields?[0].text else { return }
let task: Task = Task(title: title, done: false)
self?.tasks.append(task)
// 할 일을 등록할 때 마다 tableView 를 갱신하여 추가된 할 일이 바로 tableView 에 표시되어야 하므로
self?.tableView.reloadData()
})
let cancelButton = UIAlertAction(title: "취소", style: .cancel, handler: nil)
alert.addAction(cancelButton)
alert.addAction(registerButton)
// Alert 에 textfield 추가하는 방법
// configurationHandler - Alert 를 표시하기 전에 TextField 를 구성하기 위한 클로저
alert.addTextField(configurationHandler: { textField in
textField.placeholder = "할 일을 입력해주세요."
})
// Add 버튼을 눌렀을 때 Alert 이 실행되도록 구현
self.present(alert, animated: true, completion: nil)
}
구현 순서는 다음과 같다.
① Alert 선언 → ② Alert 에 포함되는 버튼 구현 → ③ Alert 에 버튼 / TextField 추가 → ④ 추가 버튼을 눌렀을 때 Alert 가 발생하도록 present 설정
② 등록 버튼 의 handler
handler 에서는 위 버튼을 눌렀을 때 파라미터에 정의된 클로저 함수가 실행된다. 즉, handler 에서는 사용자가 Alert의 등록 버튼을 눌렀을 때 실행해야 하는 행동을 정의한다.
이때 class 처럼 clousre 도 참조 타입이므로, closure 의 본문에서 self 로 class 의 인스턴스를 캡처할 때 강한 순환 참조가 발생할 수 있다.
ARC 단점의 단점이기도 하는데, 두 개의 객체가 상호 참조하는 경우 강한 순환 참조가 발생한다. 순환 참조에 연관된 객체들은 reference count 가 0 에 도달하지 않게 되고, 결국 메모리 누수가 발생한다.
따라서 closure 의 선언부에 [weak self] 라는 캡쳐 리스트(Capture List) 를 사용하여 강한 순환 참조를 방지한다.
[weak self]
## Capture List 란?
Closure 안에서 한 개 이상의 참조 타입(reference type)을 어떤 참조(strong, weak, unowned)로 캡쳐 할지를 정의하는 리스트. 이는 두 개(사용할 참조타입과 클로저)의 인스턴스가 강한 순환 참조가 생기는 것을 방지한다. 대괄호([])를 통해서 weak, unowned 키워드와 함께 외부에 있는 참조타입 변수를 캡쳐한다.
## TableView Update
tasks 배열에 append 하여 할 일(task)를 등록한 후, reloadData() 메서드를 통해 tableView 를 바로 갱신한다.
self?.tableView.reloadData()
③ Alert 에 TextField 를 추가할 수 있다.
alert.addTextField(configurationHandler: { textField in
textField.placeholder = "할 일을 입력해주세요."
})
configurationHandler : TextField 를 구성하기 위한 클로저.
' iOS > UIKit' 카테고리의 다른 글
URLSession 을 활용하여 도시 날씨 데이터 가져오기 (1) | 2022.08.30 |
---|---|
UICollectionView ContentView / UICollectionViewFlowLayout (0) | 2022.08.11 |
UIKit 계산기 Storyboard UI (0) | 2022.07.15 |
UIStackView (0) | 2022.07.11 |
ViewController 간 데이터 전달 (0) | 2022.07.06 |