관리 메뉴

이리메라 갖다가

[Swift] 메모 앱 만들기 심화 (2) : TableView Section/Header/Footer 본문

TIL

[Swift] 메모 앱 만들기 심화 (2) : TableView Section/Header/Footer

너이르나 2023. 8. 24. 21:01
728x90
반응형
TableView Section/Header/Footer

메모앱 만들기 심화 프로젝트를 진행하기에 앞서, 테이블뷰의 섹션을 생성하고 각 섹션마다 헤더와 푸터를 설정하는 방법에 대해 알아보자

간단한 예제 코드는 아래와 같다.

import UIKit

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    
    // 더미 데이터
    let data = [
        ["Apple", "Banana", "Cherry"],
        ["one", "two", "three"],
        ["1", "2", "3"]
    ]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let tableView = UITableView(frame: view.bounds, style: .grouped)
        tableView.dataSource = self
        tableView.delegate = self
        view.addSubview(tableView)
        
        // 테이블뷰에 헤더와 푸터 등록
        tableView.register(UITableViewHeaderFooterView.self, forHeaderFooterViewReuseIdentifier: "header")
        tableView.register(UITableViewHeaderFooterView.self, forHeaderFooterViewReuseIdentifier: "footer")
    }
    
    // 섹션 수 반환
    func numberOfSections(in tableView: UITableView) -> Int {
        return data.count
    }
    
    // 각 섹션의 셀 수 반환
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return data[section].count
    }
    
    // 셀 생성 및 데이터 삽입
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel?.text = data[indexPath.section][indexPath.row]
        return cell
    }
    
    // 섹션 헤더 뷰 설정
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "header")!
        headerView.textLabel?.text = "Section \(섹션 + 1) Header"
        return headerView
    }
    
    // 섹션 푸터 뷰 설정
    func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
        let footerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "footer")!
        footerView.textLabel?.text = "Section \(섹션 + 1) Footer"
        return footerView
    }
}

 

해당 예제를 토대로 내 프로젝트에 적용해봤는데 문제가 발생했다.

문제1. 테이블뷰의 섹션 및 헤더 Height 값을 지정했으나 시뮬레이터에 나오지 않음

override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
	return 10.0
}

override func numberOfSections(in tableView: UITableView) -> Int {
	return 5 
}
  • (원인) 테이블뷰의 스타일이 Plain로 설정되어 있음
  • (해결방안) 테이블뷰 스타일을 Grouped로 설정
    • 테이블 뷰의 레이아웃과 섹션 헤더를 표시하는 방식을 변경하기 위해서는 스타일을 Grouped로 설정해야 함
    • 섹션 헤더와 푸터가 각 섹션 위와 아래에 표시되며, 섹션과 셀 사이에 여백이 생김

문제2. 섹션으로 나누고 난 후 메모 스위치 작동 시 업데이트 안됨

  • 일단 이 문제는 지금 섹션별로 셀이 다 동일하게 들어가 있는 문제라 당장 해결할 필요는 없고 인지만 하고 있으면 될 듯

 


 

생각보다 섹션을 나누고 헤더와 푸터를 넣는 일은 어렵지 않았다.

진짜 삽질은 이제부터..

 

프로젝트에 적용하기 위해 모델의 구조에 대해 먼저 고민해보았다.

 

  • 이미 메모에는 카테고리가 있고, 그 카테고리는 디테일페이지에서 팝업버튼으로 구현해 놓은 상태
  • 섹션의 갯수는 TableViewController에 category라는 배열을 만들어 5가지를 미리 지정해 놓음
     category.count로 섹션 갯수 return
  • 만약에 카테고리를 추가로 커스텀할 수 있다고 가정하면, 해당 배열에 추가되야 하고 메뉴버튼에도 추가되어야 함(메뉴버튼 추가는 어떻게 하는지 찾아봐야 함 ㅠ)
  • 셀의 수는 for문으로 category 배열을 돌면서 if문으로 memoList.category와 동일한지 확인
  • 동일하다면 해당 카테고리만 포함하는 메모의 배열에다 추가(굳이..? 그럴거면 메모매니저에서 하는게 낫지… 근데 동일한 데이터를 여러개 만들 필요가 있을까?)
// 처음 고민
let category = ["일반", "반려동물", "집", "과제", "운동"]`
    
override func numberOfSections(in tableView: UITableView) -> Int {
	return category.count 
}
    
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
	// 카테고리 배열의 값과 memoList의 카테고리 값이 매칭되면 해당 memoList의 카운트를 반환
	for index in 0..<category.count {
		if category[index] == myMemo.memoList[index].category {
        	// 실행할 코드
        }
    return myMemo.memoList[section].count 
    }
}

 

일단 기존 데이터 구조를 살려 배열로 memoList를 유지하는 방법으로 진행했다.

 

문제3. memoList에 있는 카테고리를 Set을 통해 집합을 만들었으나 실행하니 순서가 뒤죽박죽이 됨

  • (원인) set 함수는 순서가 없음! 따라서 sorted된 변수(상수)를 만들어서 사용해야 함
  • (해결방안) let categories = Array(Set(MemoManager.shared.memoList.map { $0.category ?? "일반" })).sorted() 를 전역변수로 선언하여 사용

문제4. 디테일페이지로 들어가면 섹션의 셀의 내용이 처음 추가한 셀의 내용과 동일하게 나옴

  • (문제) 상기와 동일
  • (해결방안) 기존 tableView[indexPath.row]를 카테고리에 맞는 값으로 변경
@IBAction func memoSwitch(_ sender: UISwitch) {
    guard let tableView = superview as? UITableView,
    	let indexPath = tableView.indexPath(for: self) else {
        	return 
        }
    let categories = Array(Set(MemoManager.shared.memoList.map { $0.category ?? "일반" })).sorted()
    let category = categories[indexPath.section]
    let memoListInSection = MemoManager.shared.memoList.filter { $0.category == category }
    var memo = memoListInSection[indexPath.row]
    memo.isCompleted = sender.isOn
    updateLabelStrikeThrough()
    MemoManager.shared.updateMemo(at: indexPath.row, newContent: memo.content, isCompleted: memo.isCompleted, insertDate: memo.insertDate, targetDate: memo.targetDate, priority: memo.priority, category: memo.category, progress: memo.progress)
    // 로그 출력 (Memo 객체의 내용 출력)
    for memo in MemoManager.shared.memoList { print(memo) }
}

 

자 이제.. 그러다가 문득 필터함수에 대해 의문을 가지게 되었다.(그러지 말았어야..)

메모를 추가하고 수정하고 로드할 때마다 계속 필터로 카테고리를 찾아야 하니 메모리가 많이 사용되지 않을까 하며..

고민. filter 함수쓰면 매번 필터링을 돌아야해서 메모리를 많이 쓰지 않을까?

  • 데이터 구조 변경 : memoList = [Memo] 배열 → memoList = [String : [Memo]] 딕셔너리
  • String은 key 값이고 해당 key를 카테고리로 변경
  • 와 그냥 필터쓰는게 나앗을 듯……….

문제5. 딕셔너리로 데이터 구조를 변경하였으나, 입력값을 인식 못해 메모가 추가되지 않음

  • (원인) 카테고리가 처음에 없어서 생성해줘야 함(아니 근데 애초에 메모 추가할 때 카테고리를 지정했는데 그 value에 있는 카테고리를 key값으로 받아오지를 못하나?)

문제6. 카테고리는 만들어지는데 메모가 수정이 안됨

  • (원인) 뭔지도 모르겠음,... 
  • (원인파악) 디버깅 시도 하였으나, 브레이크 포인트가 안먹음...(어이X), 함수 사이에 프린트문을 넣어서 확인해봤는데 조건을 만족하지 못해 업데이트 코드를 실행하지 못하고 return 됨
  • 뭔가.. 기존 데이터에 없던 카테고리를 선택하게 되면서 해당 카테고리를 만들어줘야 하는 것 같음
if newMemoList[category] == nil {
    newMemoList[category] = []  // 해당 카테고리의 배열이 없다면 생성
}
  • 근데 카테고리만 만들어지고 기존 메모가 수정 적용이 안되는거보니 조건을 만족할 수 있는 범위로 변경해야 함
  • 조건문 수정해서 메모 수정하는 코드로 진입했는데 에러... out of range래...
  • 당연하지... 메모가 없는데 접근을 하려고 하니까!!!!!

결국 몇시간 부여잡고 딕셔너리 포기하고 filter로 돌아옴...

지금 필터도 셀 재사용 때문에 완벽하게 구현하지는 못했는데 이거 해결하고 나면 딕셔너리 뽀개준다.. 기다리셈..

 

728x90
반응형