Todolist (.cont)
Tag 구조체 생성 && Todo Class 변경
Tag
를 만들었는데, 따로 기능이 있지 않아서 아쉬워서 Tag
를 활용한 기능을 구현하려고 했다. 미리 알림 앱에서 보니깐 태그들을 정리 해놔서, 태그를 클릭하면, 해당 태그를 사용하는 모든 미리알림들이 나열되어 있는 기능을 보고 비슷하게 구현을 해야겠다는 생각을 했다.
- 따라서 각 태그 별로, 어떤
Todo
가 해당 태그를 사용하고 있는지의 대한 정보가 필요했다. 그리고 Struct
로 구현을 하면, 값 전달형식이기 때문에, 추후에 Tag 별로 미리 알림화면에서 변경을 할시에는 더 복잡해질수가 있기 때문에, Class
를 사용했다.
import Foundation
import UIKit
struct Tag {
var tagName : String
var color : UIColor
var todo : [Todo]
}
extension Tag {
static var tagDic : [String : Tag] = [ "#투두" : Tag(tagName: "#투두", color: UIColor(hexCode: "8FCB9B"), todo: [])]
static func convertToTagDic(todos: [Todo]) {
...
}
mutating func remove(todoID: Int) {
...
}
static func updateTagsAfterTodoDeletion(deletedTodoID: Int) {
...
}
static func printTagDictionary() {
...
}
}
- 일단
tag
의 이름, 태그별 색깔, 그리고 제일 중요한 해당 태그를 사용하고 있는 Todo 배열이다.
- 그리고
extension
을 사용해서 가독성을 높이기 위해서 사용했다.
tagDic
는 해당 태그의 이름을 Key로 설정하고, Value는 Tag를 갖고 있다.
- 굳이
Dictionary
로 한 이유는 당연히 연산속도 및 편리하기 때문이다.
Tag
가 존재하는지도 확인해야하는데, Tag 배열로는 성능적으로 아쉬울꺼 같아서 이렇게 선언했다.
- 처음 세팅되어 있는 값들은 하드코딩으로 했지만, 앱이 실행중에 추가되는건 함수를 이용했다.
convertToTagDIc(todos: [Todo])
함수는 새로운 Tag를 tagDic에 추가하는 함수다..
static func convertToTagDic(todos: [Todo]) {
for todo in todos {
for tag in todo.tag {
if let existingTag = self.tagDic[tag] {
if !existingTag.todo.contains(where: { $0.id == todo.id }) {
var updatedTodoList = existingTag.todo
updatedTodoList.append(todo)
let updatedTag = Tag(tagName: existingTag.tagName, color: existingTag.color, todo: updatedTodoList)
self.tagDic[tag] = updatedTag
}
} else {
let randomColor = UIColor(red: .random(in: 0...1), green: .random(in: 0...1), blue: .random(in: 0...1), alpha: 1.0)
let newTag = Tag(tagName: tag, color: randomColor, todo: [todo])
self.tagDic[tag] = newTag
}
}
}
}
Todo List
를 전달하면 해당 List를 TagDic에 추가해주는 함수이다.
- 각
Todo
의 Tag
를 돌면서, 해당 Tag
가 TagDic
에 있다면, 해당 Todo가 이미 추가가 되어 있는지 확인하고, 없다면 TagDic[Tag].Todo.append
를 한다.
- 만약에 없다면 랜덤 색깔을 추가해서
Tag Dic
에 추가한다. (추후에는 지정된 색깔로 넣을 계획)
- 이 함수는 즉, 새로운 tag를 추가하는 용도로 사용되는 함수다.
Remove
함수는 Tag
에서 주어진 TodoID
를 이용해서, 해당 Todo를 삭제하는 함수다.
Struct
이니깐 Mutating
을 사용했다.
- 예를 들면
"#공부"
태그를 사용하는 Todo (id : 1)
이 있다고 가정하면, #공부 tag
에서 id: 1
을 삭제하는것이다.
mutating func remove(todoID: Int) {
for (index, _) in self.todo.enumerated() {
if self.todo[index].id == todoID {
self.todo.remove(at: index)
break
}
}
}
- 각
tag
구조체가 갖고 있는 [Todo]
가 업데이트 되었으니, 이제 해당 정보로 tagDic
을 업데이트 하는 함수다.
- 만약 해당
Tag
를 사용하는 Todo
가 없다면, tagDic
에서 삭제한다.
- 해당 함수는
Todo
가 Todo List
에서 지워질때 호출된다.
- Todo에서 tag가 삭제될때 실행되는 함수는 따로 있다.
- (
Todo
가 deinit
되는 시점에 호출되면 더 좋을꺼 같다.)
static func updateTagsAfterTodoDeletion(deletedTodoID: Int) {
for (tagName, _) in tagDic {
if var tag = tagDic[tagName], tag.todo.contains(where: { $0.id == deletedTodoID }) {
tag.remove(todoID: deletedTodoID)
if tag.todo.isEmpty {
tagDic[tagName] = nil
} else {
tagDic[tagName] = tag
}
}
}
}
printTagDictionary
함수는 단순히 Print위한 함수다.
- 조금더 직관적으로 어떤
tag
를 어떤 todo
들이 사용하는지 보기 위함이다.
static func printTagDictionary() {
for (tagName, tag) in self.tagDic {
var temp = ""
temp += "Tag: \(tagName) |||| "
temp += "Todo ID: "
for todo in tag.todo {
temp += String(todo.id)
}
print(temp)
}
print("----")
}
- Tag를 추가하고 업데이트하는 함수 :
convertToTagDIc
, Todo를 Tag.todo에서 지우는 함수 : updateTagsAfterTodoDeletion
.
- 그러면 반대로,
Todo
에서 tag
가 지워졌을때, TagDic
을 업데이트 하는 함수도 만들어야 한다. 이건 Todo Class
에서 구현했다.
class Todo
var id : Int
var title : String
var isCompleted : Bool = false
var isImportant : Bool = false
var startDate : Date?
var endDate : Date?
var memo : String
var tag : [String] {
didSet {
updateTagDictionary(oldValue)
}
}
var isOpen : Bool = false
init(...){
...
}
deinit{
print("deinit")
}
}
extension Todo {
static var todoID = 5
static var list = [Todo(id: 0, title: "👇🏻 투두 눌러서 펼쳐보기!",isCompleted: false,isImportant: false, startDate:Date(), endDate: Date(timeIntervalSinceNow: 300),memo: "TEST" ,tag: ["#투두","#펼쳐보기","#iOS"],isOpen : true)]
private func updateTagDictionary(_ oldValue : [String]) {
...
}
- 달라진점은
Tag
변수에다가 didset
을 사용해서, tag
가 변하면 tagDic
를 업데이트는 걸 구현했다.
updateTagDictionary 는 태그를 삭제하면 삭제한 태그에서 해당 todo를 삭제하는 함수다.
- 앞서
Tag
에 있는 함수는, tag todo
배열에서 해당 Todo ID
를 전부 삭제하는 함수였다면, 이 함수는 하나의 tag에서 해당 Todo ID를 삭제하는 함수다.
didset
에서 호출되기 때문에, 어떤 tag
가 todo
에서 삭제됐는지 쉽게 파악할 수 있으며, 논리적으로도 맞는거 같다.
- 그러면 추가할때도 didset를 사용하면 되는거 아닌가 라는 생각을 했지만, 현재 내가 구현한 방법이 이걸 고려하지 않는 상황이다. 설명하자면 아래와 같다.
detailsViewController
에서 즉 todo
를 변경하는 view
를 넘어갈때, 새로운 투두를 만드는것인지, 아니면 기존의 투두를 변경하는 것이지 확인하는 방법으로 사용했던게 currentTodo
다
currentTodo : Todo?
의 타입을 갖고 있으며, 그냥 추가하기를 이용할때는 currentTodo는 Nil값을 갖는다, 반면, 기존 todo
를 변경하는 경우에는, currentTodo
는 기존 Todo
를 참조한다.
- 여기까지는 전혀 문제가 없는데, 문제는 변경하기 화면에서 tag를 보여주기 위한
collectionView
가 있는데, 이 collectionView
는, currentTodo.tag
를 보여주는 collectionView
다.
- 기능중 하나가
Tag
를 클릭시에, 해당 tag가 삭제가 되는데, 삭제를 하고 저장을 하지 않는다면? 정상적으로는 당연히 아무 변화가 없어야하지만, 내가 구현했던 방법은 tag
를 클릭시, currentTodo.tag
에서 해당 태그를 지워버리는것이다. 즉 저장을 하지 않아도 이미 Tag
에는 지워진 tag가 없는 것이다.
- 마찬가지로
didset
를 사용하면, 저장을 안눌러도 해당 문제가 발생하니깐 똑같은 문제다.
- 그래서 생각한 방법이, 그러면
currentTodo
의 tag
값만 저장을 해서 복사해서 사용하는것이다.
private func updateTagDictionary(_ oldValue : [String]) {
let previousTags = Set(oldValue)
let newTags = Set(tag)
let removedTags = previousTags.subtracting(newTags)
for removedTag in removedTags {
Tag.tagDic[removedTag]!.todo.removeAll{ $0.id == self.id}
if Tag.tagDic[removedTag]!.todo.isEmpty {
Tag.tagDic[removedTag] = nil
}
}
}
tableView 안에 CollectionView 업데이트 오류 수정
- 이 오류는 튜터님께서 정답을 알려주셨다.
- 간혹가다가
tableview
에 있는 cell
을 지울시, 다른 cell
안에 있는 collectionView
가 이상하게 바뀌는 현상있었는데, 이 부분은 tableView
를 cellforRowAt
함수가 호출될때, 안에있는 collectionView
도 reloadData()
를 호출해줘야지만 해결되는 문제였다.
- 재사용
Cell
의 고질적인 문제(?), 재사용되는것을 항상 고려하고 있어야하는데, Cell안에 CollectionView
가 있다보니깐 또 놓친 부분이었다.
TableView
안에 Cell
을 초기화할때는, cell
안에있는 CollectionView
까지 초기화를 진행하자!
DatePicker 표기 오류 수정
- 이 부분은 어제 사실 조치가 되었지만, 튜터님께 확실히 물어본 결과, 데이터를 어떻게 처리하냐의 따라서 다다르기 때문에, 내가 편한대로 하면된다고 해서, 나는 데이터를 그냥 UCT 기준으로 저장하고, 불러올떄는 현재 시간 기준으로 불러오는 방식으로 선택했다.
승원님은 저멀리계셔서 제가 천천히 정독하면서 공부하겠음니다.... 근데 강의보다 본 if if if로 피라미드형태...! 방금 얼핏비슷한걸 보았네요 태그색상까지 바뀌는거 대박b