[ToyProject] ToDoList(할 일 등록)

Bright Hyeon·2021년 12월 31일
1

ToyProjects [iOS]

목록 보기
2/3
post-thumbnail

🍎 To Do List

🍏 기능 상세

  • TableView에 할 일들을 추가할 수 있습니다.
  • 할 일을 삭제할 수 있습니다.
  • 할 일의 우선순위를 재정렬할 수 있습니다.
  • 할 일들을 데이터 저장소에 저장을 하여 앱을 재실행하여도 데이터가 유지되게 합니다.

🍏 활용기술

  • UITableView : 여러 개의 Cell을 가지고 있고 하나의 열과 여러 줄의 행을 지니며, 수직으로만 스크롤 가능
  • UIAlertController
  • UserDefaults

🍏 기능구현

FullCode_github_ToDoList

1. 기초 UI 디자인

  • NavigationController 생성
  • 루트뷰 Bar에 'Bar Button Item' 오브젝트 넣고, 속성관리자에서 System Item을 'Edit'으로 설정
  • Bar 버튼 우측 상단에 하나 더 추가 후 'Add'로 설정
  • Table View 추가하고, 화면에 꽉차도록 Constraints 설정
  • 속성관리자에서 Prototype Cells 수치를 설정하면, 뷰에 Cell이 나타나는데 Cell의 스타일이 Custom이면 원하는 디자인 가능. 그 외엔 기본 시스템 설정값 사용.
  • 지금 프로젝트에선 할 일만 보이면 되니까 Basic으로 설정

2. 코드작업

  • IBOutlet 변수 및 IBAction 함수 생성
  • Add(+)버튼을 눌렀을 때 할 일을 등록할 수 있는 'Alert'이 표시되도록 구현
    @IBAction func tabAddButton(_ sender: UIBarButtonItem) {
        let alert = UIAlertController(title: "MustDo", 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(title: title, done: false)
            self?.tasks.append(task)
            self?.mustDoTableView.reloadData()
        })
        let cancelButton = UIAlertAction(title: "취소", style: .cancel, handler: nil)
        alert.addAction(cancelButton)
        alert.addAction(registerButton)
        alert.addTextField(configurationHandler: { textField in
            textField.placeholder = "할 일을 입력해주세요."
        })
        self.present(alert, animated: true, completion: nil)
    }
  • Alert은 iOS에서 보통 앱 또는 기기의 상태와 관련된 중요한 정보를 전달하며, 종종 사용자에게 피드백을 요청하기 위해 사용됩니다. UIAlertController()를 통해 생성할 수 있고, UIAlertAction()을 통해 Alert을 구성하는 버튼을 생성할 수 있습니다. UIAlertAction()에는 handler라는 Parameter가 있는데 버튼이 눌릴 때 이 곳에 정의된 클로저가 호출됩니다.
  • closure는 참조타입이기 때문에 closure의 본문에서 self로 인스턴스를 캡쳐할 때 강한 순환 참조가 발생할 수있습니다. 때문에 클로저-클래스인스턴스 사이의 강한순환참조를 방지하기 위해 [weak self] 캡쳐목록을 정의하였습니다.
  • UITableViewDataSource와 UITableViewDelegate 프로토콜 채택

UITableViewDataSource는 테이블 뷰를 생성하고 수정하는데 필요한 정보를 테이블 뷰 객체에 제공합니다.

이때, 옵셔널이 아닌 메서드는 필수로 구현해줘야 합니다.

UITableViewDelegate는 테이블 뷰의 시각적인 부분을 설정하고, 행의 액션 관리, 악세서리 뷰 지원 그리고 테이블 뷰의 개별 행 편집을 도와줍니다.

  • cellForRowAt를 묻고 셀을 반환하는 메서드 블럭 안에서는 에서는 dequeueReusableCell()라는 메서드를 통해 큐를 이용해 셀을 재사용하도록 합니다. 셀의 수가 천 개, 만 개 이상으로 많아졌을 때 각 셀을 일일이 메모리에 할당할 것이 아니기때문에 메모리 낭비를 방지하기 위해 이 메서드를 이용해서 셀을 재사용하게 만드는 것입니다.
  • 그 외 삭제, 정렬 기능들도 프로토콜 속 메서드를 사용하여 구현할 수 있습니다.
extension ViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.tasks.count
    }
    
    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
        if task.done {
            cell.accessoryType = .checkmark
        } else {
            cell.accessoryType = .none
        }
        return cell
    }
    
    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        self.tasks.remove(at: indexPath.row)
        self.mustDoTableView.deleteRows(at: [indexPath], with: .automatic)
        if self.tasks.isEmpty {
            self.tabDoneButton()
        }
    }
    
    func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
        var tasks = self.tasks
        let task = tasks[sourceIndexPath.row]
        tasks.remove(at: sourceIndexPath.row)
        tasks.insert(task, at: destinationIndexPath.row)
        self.tasks = tasks
    }
}

extension ViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        var task = self.tasks[indexPath.row]
        task.done = !task.done
        self.tasks[indexPath.row] = task
        self.mustDoTableView.reloadRows(at: [indexPath], with: .automatic)
    }
}
  • 마지막으로 앱이 꺼져도 데이터가 저장되고 로드될 수 있도록 UserDefaults를 사용하여 딕셔너리 형태로 데이터를 저장합니다.
    func saveTasks() {
        let data = self.tasks.map {
            [
                "title": $0.title,
                "done": $0.done
            ]
        }
        let userDefaults = UserDefaults.standard
        userDefaults.set(data, forKey: "tasks")
    }
    
    func loadTasks() {
        let userDefaults = UserDefaults.standard
        guard let data = userDefaults.object(forKey: "tasks") as? [[String: Any]] else { return }
        // "tasks"에 대한 value가 Any?타입으로 반환되는데, 이 Any를 [[String: Any]] 로 타입캐스팅한 것.
        self.tasks = data.compactMap {
            guard let title = $0["title"] as? String else { return nil }
            guard let done = $0["done"] as? Bool else { return nil }
            return Task(title: title, done: done)
        }
    }

🍏 느낀점

  • UITableView, UserDefault를 통한 데이터 저장 및 로드, DataSource&Delegate프로토콜, UIAlertController 등 오늘도 새로운 지식을 쌓은 것 같아 행복하다.
  • 리스트형식으로 나열하고 그 버튼을 눌러 다음 항목으로 넘어가는 앱을 구현할 때 꼭 필요한 개념인 것 같다.
  • 추가하고 싶은 부분이 있다. 나의 경우, 보통 해야할 일을 기록한다면 그 일이 언제까지 끝내야하는 일인지를 함께 기록하는 경우가 많다. 그렇기에 할 일을 등록할 때에 마감날짜와 시간을 선택하고, 선택한 날짜에 따라 섹션이 분류되고 그 하위에 셀이 등록되는 식으로 기능을 추가해보고 싶다.
profile
i'm Obsessed with Swift. iOS

0개의 댓글