저번 포스팅에 이어 취소선 에러 잡는 과정 및 데이터 저장 기능들을 구현했다.
문제점
- 스위치의 상태가 변경될 때 취소선을 추가하거나 제거하는 코드가 올바르게 작용하지 않음
- 취소선이 다른 셀로 이동하거나 취소선이 삭제되지 않는 문제가 발생함
- 데이터 저장 기능 구현
튜터님의 코멘트에 나도 앱을 실행해봤더니 위와 같은 오류가 생겼다.
셀을 추가하거나 삭제할 경우에 원래 있던 취소선(Switch)들의 문제가 생긴 것이다.
스위치 변경시 상태 값(isCompleted의 상태) 전달의 과정을 구현해주는 코드가 없었으니 당연한 결과다.
ViewController에 있는 TodoData에 접근해서 isComplete의 속성 값을 변경해주어야 한다.
또한 셀이 재사용 되는 과정에서 문제가 생겼던 것 같다.
문제점을 해결해보자.
해결방법 1 - Switch가 변경될 때 addTarget을 통한 메서드 구현
테이블 뷰의 각 셀에 스위치의 상태가 변경될 때 호출되는 메서드를 정의했다.
addTarget 이벤트 추가
//tableView의 cellForRowAt 함수 내부에 작성
cell.isCompletedSwitch.tag = indexPath.row
cell.isCompletedSwitch.addTarget(self, action: #selector(tapSwitchButton(sender:)), for: .valueChanged)
- cell.isCompletedSwitch.tag = indexPath.row : 테이블 뷰의 각 셀에 스위치를 추가할 때 해당 스위치의 태그를 설정
(태그를 이용해 어떤 셀의 스위치가 눌렸는지 식별 가능하다! 이 때 스위치의 태그 값은 해당하는 셀의 행의 인덱스로 설정해줬다.)
- addTarget(for: .valueChanged) : 각 스위치의 값이 변경되는 이벤트가 발생했을 때 tapSwitchButton 메서드가 호출되도록 설정
테이블 뷰 셀 안의 해당 스위치의 상태 변경 이벤트를 처리하기 위해 코드를 수정했다.
이제 스위치의 값이 변경되었을 때 호출되는 메서드를 작성해보자.
완료 값 update & reloadData()
@objc func tapSwitchButton(sender: UISwitch) {
print(sender.tag)
let indexPath = IndexPath(row: sender.tag, section: 0)
var todo = todoData[indexPath.row]
todo.isCompleted = sender.isOn
// 업데이트된 todo 객체로 todoData 배열을 다시 할당
todoData[indexPath.row] = todo
// 변경된 데이터를 UserDefaults에 저장
saveTasks()
print(todoData)
todoTableView.reloadData()
}
스위치의 값이 변경될 때 호출되는 tapSwitchButton() 메서드를 위와 같이 작성해줬다.
스위치를 누르면 해당 스위치의 태그를 통해 어떤 할 일 항목이 변경되었는지 식별한다. 그런 다음 해당 인덱스의 할 일 항목의 완료 여부를 스위치의 상태에 따라 업데이트하고, 변경된 데이터를 UserDefaults에 저장한다. 마지막으로 테이블 뷰를 새로 고쳐서 변경된 내용이 화면에 반영되도록 했다.
코드를 자세히 살펴보면 아래와 같다.
- todo.isCompleted = sender.isOn : 할 일 항목의 완료 여부를 스위치의 상태에 따라 업데이트
- todoData[indexPath.row] = todo : 완료 여부 다시 'todoData' 배열에 할당하며 실제 데이터 업데이트
- reloadData() : 테이블 뷰의 전체 영역를 다시 로드함 ➡️ 변경 내용 화면에 실시간으로 반영
해결방법 2 - Cell의 재사용 문제 해결
완료 여부의 데이터 전달 문제를 해결했더니 isCompleted의 데이터의 값은 잘 바뀌고 전달되지만
취소선이 제멋대로 움직이는 문제가 생겼다..!🤯
prepareForReuse()
cell 파일 안에서 초기화 작업이 필요하다.
prepareForReuse 메서드를 재정의하여 셀이 재사용될 때 실행되도록 작성했다.
이 메서드는 UITableViewCell 클래스에 정의된 메서드로, 셀이 재사용되기 직전에 호출된다는 점!
override func prepareForReuse() {
super.prepareForReuse()
isCompletedSwitch.isOn = false
todoLabel.attributedText = nil
}
스위치의 상태와 라벨의 attributeText를 제거했다. 이렇게 함으로써 이전에 셀에 표시되었던 스위치 상태와 텍스트 속성을 지우고 새로운 데이터로 업데이트 할 수 있다.
이 작업을 통해 테이블 뷰에서 셀이 스크롤되고 재사용되는 동작에 따른 이전의 데이터와 새로운 데이터의 충돌문제를 방지할 수 있다.
이렇게 하니 취소선 기능이 완벽히 구현되었다.
UserDefaults 를 사용한 데이터 저장
iOS 앱에서 데이터를 저장하기 위한 방법 중 하나로 UserDafaults로 데이터를 저장하는 방법이 있다.
UserDefaults는 앱이 실행되는 동안(런타임) Key-Value 형태로 데이터를 저장하는 사용자의 기본 데이터베이스에 대한 인터페이스다.
UserDefaults에 저장되는 데이터는 사용자 기기의 저장공간 중 앱의 "문서 및 데이터" 영역을 차지하므로,
대용량의 데이터를 저장하기보다 가벼운 단일 데이터 값을 저장하는게 좋다.
더 자세한 건 아래 공식문서를 참고하기!
saveTasks()
- 데이터를 UserDefaults에 저장
func saveTasks() {
let data = self.todoData.map {
[
"id": $0.id,
"title": $0.title,
"isCompleted": $0.isCompleted
]
}
let userDafaults = UserDefaults.standard
userDafaults.set(data, forKey: "todoData")
}
UserDefaults 객체를 생성하고 todoData 배열에 있는 각 할 일 항목을 UserDefaults에 저장 가능한 형식으로 변환하여 저장하는 로직의 메서드를 작성했다.
- self.todoData.map { ... } : 배열에 있는 각 요소를 딕셔너리 형태로 변환한다.
- userDefaults.set(data, forKey: "todoData") : 변환된 데이터를 UserDefaults에 저장한다. 이 때, "todoData"라는 키를 사용하여 데이터를 저장!
이 메서드는 앱의 할 일 목록 데이터를 UserDefaults에 저장하여 앱이 다시 시작되거나 다음에 사용될 때 이전의 데이터를 유지할 수 있도록 한다.
loadTasks()
- 데이터를 앱에 다시 불러오기
func loadTasks() {
let userDafaults = UserDefaults.standard
guard let data = userDafaults.object(forKey: "todoData") as? [[String: Any]] else { return }
self.todoData = data.compactMap{
guard let id = $0["id"] as? Int else { return nil }
guard let title = $0["title"] as? String else { return nil }
guard let isCompleted = $0["isCompleted"] as? Bool else { return nil }
return TodoData(title: title, isCompleted: isCompleted)
}
}
UserDefaults에서 저장된 할 일 목록 데이터를 불러와서 앱에 다시 불러오는 메서드도 구현해줬다.
위의 코드는 UserDefaults에서 "todoData" 키에 해당하는 데이터를 가져와서 앱의 todoData 배열에 다시 할당한다.
이를 통해 이전에 저장한 할 일 목록 데이터를 불러와서 앱이 종료되었다가 다시 시작되어도 이전의 데이터를 유지할 수 있다!
위와 같이 데이터를 저장하고 로드하는 메서드를 작성해줬으니 이제 뷰가 로드될 때, 데이터가 변경될 때 호출해서 사용해주면 데이터 저장 문제는 해결된다.😊
var todoData = [TodoData]() {
didSet {
self.saveTasks()
}
}
나는 didSet 프로퍼티 옵저버를 작성했다.
이 작업으로 todoData 가 변경될 때마다 유저 디폴트에 할일이 저장된다.
아래 화면처럼 앱을 종료했다 실행해도 데이터가 저장되어 있는 것을 확인했다.
✔️하고 싶은 기능들
- UI 꾸미기
- cell에 속성 추가
- 편집, 삭제 버튼 기능 추가
- 할 일 카테고리 추가
- 추가 또는 삭제 시 애니메이션 추가
기간 내에 다 할 수 있을까 싶지만...? ㅎ.ㅎ
오늘은 오류 해결하는데 시간을 많이 써버려서 새로운 기능을 추가할 겨를이 없었다.
그래도 오류를 고민하고 해결한 점이 뿌듯하다. 내일은 기능을 고민해서 열심히 추가해봐야겠다!
'TIL✏️' 카테고리의 다른 글
[iOS] Swift로 TodoList App 만들기(4) - 편집 모드(셀 이동) (2) | 2024.03.28 |
---|---|
[iOS] Swift로 TodoList App 만들기(3) - 카테고리 추가 (2) | 2024.03.27 |
[iOS] Swift로 TodoList App 만들기 (4) | 2024.03.26 |
[Swift] 숫자 야구 게임 (1) | 2024.03.18 |
[Swift] Calculator 기능 구현하기 (1) | 2024.03.18 |