3월 22일 TIL (TodoList)

이승원·2024년 3월 22일
1

TIL

목록 보기
49/75
post-thumbnail

Todolist (.cont)

  • 오늘은 Todolist을 어느정도 끝을 낸거 같다...
  • Tag 구조체 생성
  • Todo 구조체 -> Class로 변경
  • TableView 안에 CollectionView 업데이트 오류 수정
  • DatePicker 표기 오류 수정
  • Tag 색깔 변경 기능 구현 (진행 중)

Tag 구조체 생성 && Todo Class 변경

  • Tag를 만들었는데, 따로 기능이 있지 않아서 아쉬워서 Tag를 활용한 기능을 구현하려고 했다. 미리 알림 앱에서 보니깐 태그들을 정리 해놔서, 태그를 클릭하면, 해당 태그를 사용하는 모든 미리알림들이 나열되어 있는 기능을 보고 비슷하게 구현을 해야겠다는 생각을 했다.
  • 따라서 각 태그 별로, 어떤 Todo 가 해당 태그를 사용하고 있는지의 대한 정보가 필요했다. 그리고 Struct로 구현을 하면, 값 전달형식이기 때문에, 추후에 Tag 별로 미리 알림화면에서 변경을 할시에는 더 복잡해질수가 있기 때문에, Class를 사용했다.

  • 일단 tag 구조체는 아래와 같이 구현했다.
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에 추가해주는 함수이다.
    • TodoTag 를 돌면서, 해당 TagTagDic에 있다면, 해당 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) {
        // 해당 Todo를 사용하고 있는 Tag들을 찾아 업데이트합니다.
        for (index, _) in self.todo.enumerated() {
            if self.todo[index].id == todoID {
                self.todo.remove(at: index)
                break
            }
        }
    }

updateTagsAfterTodoDeletion 함수는 변경된 태그를 tagDic에 업데이트 하는 함수다.

  • tag 구조체가 갖고 있는 [Todo]가 업데이트 되었으니, 이제 해당 정보로 tagDic을 업데이트 하는 함수다.
  • 만약 해당 Tag를 사용하는 Todo가 없다면, tagDic에서 삭제한다.
  • 해당 함수는 TodoTodo List에서 지워질때 호출된다.
  • Todo에서 tag가 삭제될때 실행되는 함수는 따로 있다.
  • (Tododeinit되는 시점에 호출되면 더 좋을꺼 같다.)
static func updateTagsAfterTodoDeletion(deletedTodoID: Int) {
        // Tag 딕셔너리를 순회하면서 삭제된 Todo를 사용하고 있는 Tag를 업데이트합니다.
        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 // 사용되지 않는 Tag는 삭제합니다.
                } 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에서 구현했다.

  • 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에서 호출되기 때문에, 어떤 tagtodo에서 삭제됐는지 쉽게 파악할 수 있으며, 논리적으로도 맞는거 같다.
  • 그러면 추가할때도 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를 사용하면, 저장을 안눌러도 해당 문제가 발생하니깐 똑같은 문제다.
    • 그래서 생각한 방법이, 그러면 currentTodotag 값만 저장을 해서 복사해서 사용하는것이다.
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가 이상하게 바뀌는 현상있었는데, 이 부분은 tableViewcellforRowAt 함수가 호출될때, 안에있는 collectionViewreloadData()를 호출해줘야지만 해결되는 문제였다.
  • 재사용Cell의 고질적인 문제(?), 재사용되는것을 항상 고려하고 있어야하는데, Cell안에 CollectionView가 있다보니깐 또 놓친 부분이었다.
  • TableView 안에 Cell을 초기화할때는, cell 안에있는 CollectionView까지 초기화를 진행하자!

DatePicker 표기 오류 수정

  • 이 부분은 어제 사실 조치가 되었지만, 튜터님께 확실히 물어본 결과, 데이터를 어떻게 처리하냐의 따라서 다다르기 때문에, 내가 편한대로 하면된다고 해서, 나는 데이터를 그냥 UCT 기준으로 저장하고, 불러올떄는 현재 시간 기준으로 불러오는 방식으로 선택했다.
profile
개발자 (진)

1개의 댓글

comment-user-thumbnail
2024년 3월 22일

승원님은 저멀리계셔서 제가 천천히 정독하면서 공부하겠음니다.... 근데 강의보다 본 if if if로 피라미드형태...! 방금 얼핏비슷한걸 보았네요 태그색상까지 바뀌는거 대박b

답글 달기