Notice
Recent Posts
Recent Comments
Link
๊ด€๋ฆฌ ๋ฉ”๋‰ด

lgvv98

ch13 Todo ๋ฆฌ์ŠคํŠธ ์ฝ”๋“œ๋ฆฌ๋ทฐ ๋ณธ๋ฌธ

โš ๏ธ deprecated โš ๏ธ/ํŒจ์บ (์˜ฌ์ธ์›)

ch13 Todo ๋ฆฌ์ŠคํŠธ ์ฝ”๋“œ๋ฆฌ๋ทฐ

๐Ÿฅ• ์บ๋Ÿฟ๋งจ 2021. 6. 26. 18:17

โœ… ์ด๋ฒˆ์‹œ๊ฐ„์—๋Š” Todo ๋ฆฌ์ŠคํŠธ์— ๋Œ€ํ•ด์„œ ๋ฆฌ๋ทฐํ•˜๋„๋ก ํ•ด๋ณผ๊ฒŒ.

์ด๊ฒŒ ๊ธˆ๋ฐฉ ๋๋‚ ์ค„ ์•Œ์•˜๋Š”๋ฐ, ์ƒ๊ฐ๋ณด๋‹ค ๋ฐฐ์šธ๊ฒŒ ๋งŽ์€ ์‹œ๊ฐ„์ด์—ˆ๋‹ค. (๋ชจ๋ฅด๋Š”๊ฒŒ ์™œ ์ ์  ๋” ๋งŽ์•„์ง€์ง€?)

๐Ÿคฆ‍โ™‚๏ธ ๊นƒ์— ๋Œ€ํ•ด ์‚ฌ์šฉ์ด ๋ฏธ์ˆ™ํ•œ๋ฐ ์‹ค์ˆ˜๋ฅผ ํ•˜๋Š” ๋ฐ”๋žŒ์—,, ๊นƒํ—ˆ๋ธŒ๋ฅผ ๋‹ค์‹œ ํ•˜^_^_^_^_^_

 

-> ๊นƒํ—ˆ๋ธŒ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•œ ํฌ์ŠคํŒ…

https://rldd.tistory.com/117

 

๐Ÿคฆ‍โ™‚๏ธ git ์›๊ฒฉ ์ €์žฅ์†Œ์— ์˜ฌ๋ผ๊ฐ„ commit ๋˜๋Œ๋ฆฌ๊ธฐ

์ด๋ฒˆ์—๋Š” ๊นƒ ์›๊ฒฉ ์ €์žฅ์†Œ์— ์˜ฌ๋ผ๊ฐ„ commit ๋˜๋Œ๋ฆฌ๋Š” ๋ฒ•์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด์ž. (๋ฌธ์ œ) ๊นƒํ—ˆ๋ธŒ์— ์ˆ˜๋™์œผ๋กœ ํŒŒ์ผ์„ ์ถ”๊ฐ€ํ•˜์˜€๋Š”๋ฐ, ๋‚˜์ค‘์— ์•Œ๊ณ ๋ณด๋‹ˆ๊นŒ ์ฝ”๋“œ๊ฐ€ ์ž˜๋ชป๋˜์—ˆ์—ˆ์Œ. ๋”ฐ๋ผ์„œ ๊นƒ ํ—ˆ๋ธŒ์—์„œ ๋‹ค์‹œ ์ˆ˜

rldd.tistory.com

 

๊ทธ๋Ÿผ ์ด์ œ ๋‹ค์‹œ ์ฝ”๋“œ ๋ฆฌ๋ทฐ๋ฅผ ์‹œ์ž‘ํ•ด ๋ณด๋„๋ก ํ• ๊นŒ

https://github.com/lgvv/fastCampus/tree/main/TodoList

 

lgvv/fastCampus

Contribute to lgvv/fastCampus development by creating an account on GitHub.

github.com

 

 

(๋ชฉ์ฐจ)

1. ViewDidAppear ์—๋Š” ์ธ์ž๊ฐ’์ด ๋“ค์–ด๊ฐ„๋‹ค!

2. guard๋ฌธ์— ๋Œ€ํ•ด์„œ ๋‹ค์‹œ๊ธˆ ์งš๊ณ ๊ฐ€์ž.

3. collectionView ์„น์…˜์— ๋”ฐ๋ผ ๋‹ค๋ฅด๊ฒŒ ๊ตฌ์„ฑํ•˜๋Š” ๋ฒ•

4. collectionView ์„น์…˜์— ๋”ฐ๋ฅธ ์•„์ดํ…œ์€ ์–ด๋–ป๊ฒŒ ๊ตฌ์„ฑํ• ๊นŒ?

5. Todo - MVVM ๊ด€์ ์—์„œ์˜ ๋ถ„์„

6. Storage ํŒŒ์ผ์— ๋Œ€ํ•œ ๋ถ„์„

 

 

โœ… ViewDidAppear ์—๋Š” ์ธ์ž๊ฐ’์ด ๋“ค์–ด๊ฐ„๋‹ค!

    // viewDidLoad - ๋ฉ”๋ชจ๋ฆฌ์— ์˜ฌ๋ผ์™”์„ ๋•Œ
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    // viewDidAppear - ํ™”๋ฉด์— ๋ณด์—ฌ์งˆ ๋•Œ
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
    }

๋ณด์ด๋Š”๊ฐ€..? viewDidAppear์—๋Š” animated๋ผ๋Š” ์ธ์ž๊ฐ’์ด ๋“ค์–ด๊ฐ„๋‹ค. ํฌ๊ฒŒ ์ค‘์š”ํ•œ๊ฑด ์•„๋‹ˆ์ง€๋งŒ, ์—๋Ÿฌ๋ฅผ ํ•˜๋„ ๋‚ด์„œ... ๊ณต์‹๋ฌธ์„œ๋ฅผ ์ฝ์–ด๋ณด๋ฉด ์‰ฝ๊ฒŒ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋‹ค.

 

<viewDidLoad>

https://developer.apple.com/documentation/uikit/uiviewcontroller/1621495-viewdidload

<ViewDidAppear>

https://developer.apple.com/documentation/uikit/uiviewcontroller/1621423-viewdidappear

 

 

 

โœ… guard๋ฌธ์— ๋Œ€ํ•ด์„œ ๋‹ค์‹œ๊ธˆ ์งš๊ณ  ๊ฐ€์ž.

guard let detail = inputTextField.text, detail.isEmpty == false else { return }

์ด๋Ÿฐ ์ฝ”๋“œ๊ฐ€ ์žˆ์—ˆ๋‹ค. guard๋ฌธ์— ๋Œ€ํ•ด์„œ๋Š” ๋งค๋ฒˆ ํ—ท๊ฐˆ๋ฆฐ๋‹ค...

guard๋ฌธ์€ let ์•ˆ์˜ ์กฐ๊ฑด์ด true ์ด๋ฉด ์ง€๋‚˜๊ฐ€๊ณ , ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด return ๊ตฌ๋ฌธ์„ ์‹คํ–‰ํ•จ์œผ๋กœ์จ ์‚ฌ์šฉ ๋ผ

ํ”„๋กœ๊ทธ๋žจ์˜ ์ฃฝ๋Š” ์ƒํ™ฉ์„ ๋ง‰์„ ์ˆ˜ ์žˆ์–ด์„œ ๋น„๊ต์ ์œผ๋กœ ์•ˆ์ „ํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, if let๊ณผ ๋น„๊ต๋˜๊ณค ํ•œ๋‹ค.

์ด์— ๋Œ€ํ•œ ๋” ์ดํ•ด๋Š” ์ฐธ๊ณ ์ž๋ฃŒ๋กœ ๋‚จ๊ฒจ๋‘๋„๋ก ํ• ๊ฒŒ

์ž ๊ทธ๋ž˜์„œ ์ € ์ฝ”๋“œ๋ฅผ ํ•ด์„ํ•ด๋ณด๋ฉด, inputTextField.text๊ฐ€ ์กด์žฌํ•ด์„œ text์— ๋ฌด์—‡์ด๋ผ๋„ ์ž…๋ ฅ๋˜์–ด ์žˆ๋‹ค๋ฉด! ์Šค๋ฌด์Šคํ•˜๊ฒŒ ๋„˜์–ด๊ฐ€๊ณ  ๊ทธ๋ ‡์ง€ ์•Š๋‹ค๋ฉด return ํ•˜๋ผ๋Š” ์˜๋ฏธ์ด๋‹ค.

 

โœ… collectionView ์„น์…˜์— ๋”ฐ๋ผ ๋‹ค๋ฅด๊ฒŒ ๊ตฌ์„ฑํ•˜๋Š” ๋ฒ•

 

   func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        // TODO: ์„น์…˜๋ณ„ ์•„์ดํ…œ ๋ช‡๊ฐœ
        if section == 0 {
            return todoListViewModel.todayTodos.count
        } else {
            return todoListViewModel.upcompingTodos.count
        }
    }

์œ„์˜ ์ฝ”๋“œ์—์„œ ์ฒ˜๋Ÿผ ์„น์…˜์— ๋”ฐ๋ผ ๋‹ค๋ฅด๊ฒŒ ์ค„ ์ˆ˜ ์žˆ์–ด. 

 

 

โœ… collectionView ์„น์…˜์— ๋”ฐ๋ฅธ ์•„์ดํ…œ์€ ์–ด๋–ป๊ฒŒ ๊ตฌ์„ฑํ• ๊นŒ?

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        // TODO: ์ปค์Šคํ…€ ์…€
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "TodoListCell", for: indexPath) as? TodoListCell else {
            return UICollectionViewCell()
        }
        
        var todo: Todo
        if indexPath.section == 0 {
            todo = todoListViewModel.todayTodos[indexPath.item]
        } else {
            todo = todoListViewModel.upcompingTodos[indexPath.item]
        }
        cell.updateUI(todo: todo)
        
        // TODO: todo ๋ฅผ ์ด์šฉํ•ด์„œ updateUI
        // TODO: doneButtonHandler ์ž‘์„ฑ
        // TODO: deleteButtonHandler ์ž‘์„ฑ
        
        cell.doneButtonTapHandler = { isDone in
            todo.isDone = isDone
            self.todoListViewModel.updateTodo(todo)
            self.collectionView.reloadData()
        }
        
        cell.deleteButtonTapHandler = {
            self.todoListViewModel.deleteTodo(todo)
            self.collectionView.reloadData()
        }
        
        return cell
    }

MVVM ํŒจํ„ด์ด๋ผ์„œ var todo : Todo ํƒ€์ž…์ธ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด.

๋˜ํ•œ ๋ทฐ ๋ชจ๋ธ์— ๋”ฐ๋ผ์„œ todo์— ์„œ๋กœ ๋‹ค๋ฅธ ์ •๋ณด๋ฅผ ๋ถ€์—ฌํ•จ์œผ๋กœ์จ, ์„น์…˜์— ๋งž๊ฒŒ ์•„์ดํ…œ์„ ๋ฐฐ์น˜ํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์ง€.

MVC ํŒจํ„ด์—์„œ cell.titleLabel ์ด๋Ÿฐ์‹์œผ๋กœ ์ ‘๊ทผํ•˜๋Š” ๊ฒƒ๊ณผ๋Š” ๋‹ค๋ฅด๊ฒŒ ์šฐ์„  ๊ฐ ์„น์…˜์—์„œ todo์— ๋„ฃ๋Š” ๋ชจ์Šต์„ ๋ณผ ์ˆ˜ ์žˆ์–ด.

๐Ÿ’ก์—ฌ๊ธฐ์„œ ์˜๋ฌธ!! todo๋Š” ๋ฐฐ์—ด์ด ์•„๋‹Œ๋ฐ, ์–ด๋–ป๊ฒŒ ์—ฌ๋Ÿฌ๊ฐœ์˜ ์•„์ดํ…œ์„ ๋ณด์—ฌ์ค„ ์ˆ˜๊ฐ€ ์žˆ๋Š”๊ฑธ๊นŒ?

-> ํ•ด๋‹ต์€ cellForItem ๋ฉ”์†Œ๋“œ๋Š” ์…€์˜ ๊ฐฏ์ˆ˜๋งŒํผ ๋ฐ˜๋ณต๋˜์„œ ์‹คํ–‰ ๋ผ

์ฆ‰, ์…€์ด 10๊ฐœ๋ฉด ์ด ํ•จ์ˆ˜ ์ž์ฒด๊ฐ€ 10๋ฒˆ ์‹คํ–‰๋˜๋Š”๊ฑฐ๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋ผ.

 

cell์ด UI๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ๋ถ€๋ถ„์„ ํ•œ๋ฒˆ ๋ด๋ณผ๊นŒ?

    func updateUI(todo: Todo) {
        // TODO: ์…€ ์—…๋ฐ์ดํŠธ ํ•˜๊ธฐ
        checkButton.isSelected = todo.isDone
        descriptionLabel.text = todo.detail
        descriptionLabel.alpha = todo.isDone ? 0.2 : 1 // ์™„๋ฃŒ๋˜์—ˆ์œผ๋ฉด ํˆฌ๋ช… ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ๋ถˆํˆฌ๋ช…
        deleteButton.isHidden = todo.isDone == false
        showStrikeThrough(todo.isDone) // ๋ ˆ์ด์•„์›ƒ์˜ ๊ธธ์ด ์กฐ์ ˆ

    }

updateUI๋Š” ์ด๋ ‡๊ฒŒ ๊ตฌ์„ฑ๋˜์—ˆ๋Š”๋‹ค.

checkButton์€ todo์˜ isDone์„ ํ†ตํ•ด true, false๋ฅผ ํ™•์ธํ•œ๋‹ค.

descriptionLabel์— alpha๋ฅผ ์คŒ์œผ๋กœ์จ isDone ์ƒํƒœ์— ๋”ฐ๋ผ ํˆฌ๋ช… ๋ถˆํˆฌ๋ช…์„ ์„ค์ •ํ•˜๊ฒŒ ํ•œ๋‹ค.

deleteButton์˜ ๊ฒฝ์šฐ์—๋Š” ์ฝ”๋“œ๊ฐ€ ์กฐ๊ธˆ ํŠน์ดํ•œ๋ฐ, ๋ณ€์ˆ˜๋ฅผ ํ•˜๋‚˜ ๋”ฐ๋กœ ๋‘์–ด์„œ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ์ง€๋งŒ ๊ตณ์ด ๊ทธ๋ ‡๊ฒŒ ํ•˜์ง€ ์•Š์•„๋„, todo.isDone์ด true์ธ ๊ฒฝ์šฐ ์„ ํƒํ–ˆ๋‹ค๋Š” ์˜๋ฏธ๋ผ ์‚ญ์ œ ๋ฒ„ํŠผ์„ ํ™œ์„ฑํ™” ํ•ด์•ผํ•˜๋Š”๋ฐ, ๋ถˆ๋ฆฌ์–ธ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ž‘์„ฑํ•˜์˜€๋‹ค.

์ฆ‰, todo.isDone์ด false์™€ ๊ฐ™์€๊ฒฝ์šฐ true๋ฅผ ๋ฐ˜ํ™˜ ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด false๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

๋…ผ๋ฆฌํšŒ๋กœ ์‹œ๊ฐ„์— ๋ฐฐ์šด ๋…ผ๋ฆฌ์‹์„ ๋– ์˜ฌ๋ฆฌ๋ฉด ์‰ฝ๋‹ค.

๋งˆ์ง€๋ง‰์œผ๋กœ showStrikeThrough๋Š” ๋ ˆ์ด์•„์›ƒ์˜ ๊ธธ์ด ์กฐ์ ˆํ•˜๋Š” ๊ฑด๋ฐ, ์ด๊ฑด ๋‹ค์Œ ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ๋ณด์ž.

 

showStrikeThrough ๋ฉ”์†Œ๋“œ์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด์ž.

   private func showStrikeThrough(_ show: Bool) {
        if show {
            strikeThroughWidth.constant = descriptionLabel.bounds.width
        } else {
            strikeThroughWidth.constant = 0
        }
    }

๋ทฐ๋ฅผ ํ•˜๋‚˜ ์ถ”๊ฐ€ํ–ˆ๋‹ค. ์šฐ์ธก ์ด๋ฏธ์ง€์˜ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘๋™ํ•œ๋‹ค.
showStrikeThrough์— ๋Œ€ํ•œ ์ •๋ณด ์ถ”๊ฐ€!

 

โญ๏ธ showStrikeThrough ๋ฉ”์†Œ๋“œ๋Š” ๊ทธ๋ƒฅ ๋ทฐ๋ฅผ ํ•˜๋‚˜ ํšŒ์ƒ‰์œผ๋กœ ๊ฝ‰ ์ฑ„์›Œ์„œ ํ•˜๋‚˜์˜ ์„ ๊ณผ ๊ฐ™์€ ํšจ๊ณผ๋ฅผ ์—ฐ์ถœํ•œ๋‹ค. (๋‚˜๋ฆ„ skill)

 

๊ทธ๋Ÿผ ๋งˆ์ง€๋ง‰์œผ๋กœ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋ณผ๊นŒ?

์ด๊ฑด ๋ฌธ๋ฒ•๊ณผ ๊ด€๋ จํ•œ ๋ถ€๋ถ„์ธ๋ฐ ์•„๋ž˜ ์ฐธ๊ณ  ๋ถ€๋ถ„์— ์šฐ์„  ๋„ฃ์–ด๋‘๊ธฐ๋„ ํ–ˆ๋‹ค

 var doneButtonTapHandler: ((Bool) -> Void)?
 var deleteButtonTapHandler: (() -> Void)?

ํ•ธ๋“ค๋Ÿฌ์— ๋Œ€ํ•œ ๋ถ€๋ถ„์ด๋‹ค. 

completion Handler์— ๋Œ€ํ•œ ๋ถ€๋ถ„์€ ๊ผญ ์ฐธ๊ณ ๋ฅผ ๋ณด์•˜์œผ๋ฉด ์ข‹๊ฒ ๋‹ค. ์ดํ•ด๊ฐ€ ํ›จ์”ฌ ์‰ฝ๋‹ค...!

 

 

โœ… Todo - MVVM ๊ด€์ ์—์„œ์˜ ๋ถ„์„

// TODO: Codable๊ณผ Equatable ์ถ”๊ฐ€
struct Todo: Codable, Equatable {
    let id: Int
    var isDone: Bool
    var detail: String
    var isToday: Bool
    
    mutating func update(isDone: Bool, detail: String, isToday: Bool) {
        // TODO: update ๋กœ์ง ์ถ”๊ฐ€
        self.isDone = isDone
        self.detail = detail
        self.isToday = isToday
        
    }
    
    static func == (lhs: Self, rhs: Self) -> Bool {
        // TODO: ๋™๋“ฑ ์กฐ๊ฑด ์ถ”๊ฐ€
        return lhs.id == rhs.id
    }
}

๋‚˜์˜ ๋‹ค๋ฅธ ํฌ์ŠคํŒ…์—์„œ Equatable์— ๋Œ€ํ•ด์„œ ํฌ์ŠคํŒ… ํ–ˆ์ง€๋งŒ, ์‰ฝ๊ฒŒ ๋งํ•ด์„œ ์Šค์œ„ํ”„ํŠธ์—์„œ ๊ธฐ๋ณธ ํƒ€์ž…์ด ์•„๋‹Œ ๊ตฌ์กฐ์ฒด๋‚˜ ํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค๊ฐ„์˜ ๊ฐ’ ๋น„๊ต๋ฅผ ์œ„ํ•ด์„  ๋‚ด๊ฐ€ ๋”ฐ๋กœ ์ •์˜ํ•ด์•ผ ํ•˜๋Š”๋ฐ ๊ทธ์— ํ•„์š”ํ•œ ๊ฒƒ์ด๋‹ค.

๋‹ค์Œ์€ โญ๏ธCodable!! ์ด ์นœ๊ตฌ๋Š” ๋‚ด๊ฐ€ ๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ๋‚˜ ๊ณต๋ถ€์—์„œ ๋งŽ์ด ๋ณด์•˜์—ˆ๋Š”๋ฐ, ์ œ๋Œ€๋กœ ์ดํ•ด๊ฐ€ ์–ด๋ ค์› ๋‹ค... 

์•„๋ฌดํŠผ ์ด๋ฒˆ ์‹œ๊ฐ„์—๋Š” ๊ฐœ๋…์— ๋Œ€ํ•ด์„œ ๊ฐ„๋žตํžˆ ์งš๊ณ  ๋„˜์–ด๊ฐ€๊ณ  ๋‹ค์Œ์—๋Š” ์ง์ ‘ ์ฝ”๋“œ๋กœ ํŒŒ์‹ฑํ•˜๋Š” ์—ฐ์Šต๊นŒ์ง€ ํ•ด๋ณด๋„๋ก ํ•˜์ž.

Codable์ด๋ž€, JSONํ˜•ํƒœ๋กœ ์žˆ๋Š” ํŒŒ์ผ์„ key๋ฅผ ๋ณ€์ˆ˜๋ช…์œผ๋กœ ์‚ผ์•„์„œ, ์šฐ๋ฆฌ๊ฐ€ ์†์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“ค์–ด์ฃผ๋Š” ๊ฒƒ์ด๋‹ค.

๋‹ค์Œ์€ mutating ํ‚ค์›Œ๋“œ์— ๋Œ€ํ•ด์„œ ๋ณด์ž.

์‰ฝ๊ฒŒ ๋งํ•ด์„œ ๊ตฌ์กฐ์ฒด ๋‚ด์—์„œ ๋ฐ์ดํ„ฐ์˜ ๊ฐ’์„ ์ˆ˜์ •ํ•ด ์ฃผ๊ธฐ ์œ„ํ•ด์„œ ์žˆ์–ด์•ผ ํ•œ๋‹ค๊ณ  ํ•œ๋‹ค..!

 

๋งˆ์ง€๋ง‰ func == ์€ ๋‚˜์˜ ํฌ์ŠคํŒ…์„ ์ฐธ๊ณ ํ•˜๊ธฐ ๋ฐ”๋žŒ.

https://rldd.tistory.com/114

 

๐Ÿ˜‚ ch13 swift Equatable?!

โœ… ์ด๋ฒˆ ์‹œ๊ฐ„์—๋Š” Equtable์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด๋„๋ก ํ• ๊ฒŒ. Equatable์ด ๋ญ๋ƒ? ๋‘ ๊ฐ’์ด ๋™์ผํ•œ์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ํ”„๋กœํ† ์ฝœ์ด์•ผ. ์šฐ๋ฆฌ๊ฐ€ ์ฝ”๋”ฉ์„ ํ•˜๋ฉด์„œ "abc" == "abc" ํ˜น์€ 33 != 33 ๋“ฑ ์Šค์œ„ํ”„ํŠธ์˜ ๊ธฐ๋ณธ ํƒ€์ž…์„

rldd.tistory.com

 

 

๋‹ค์Œ์€ ViewModel์— ๋Œ€ํ•ด์„œ ๋ณด์ž

class TodoViewModel {
    
    enum Section: Int, CaseIterable {
        case today
        case upcoming
        
        var title: String {
            switch self {
            case .today: return "Today"
            default: return "Upcoming"
            }
        }
    }
    
    private let manager = TodoManager.shared
    
    var todos: [Todo] {
        return manager.todos
    }
    
    var todayTodos: [Todo] {
        return todos.filter { $0.isToday == true }
    }
    
    var upcompingTodos: [Todo] {
        return todos.filter { $0.isToday == false }
    }
    
    var numOfSection: Int {
        return Section.allCases.count
    }
    
    func addTodo(_ todo: Todo) {
        manager.addTodo(todo)
    }
    
    func deleteTodo(_ todo: Todo) {
        manager.deleteTodo(todo)
    }
    
    func updateTodo(_ todo: Todo) {
        manager.updateTodo(todo)
    }
    
    func loadTasks() {
        manager.retrieveTodo()
    }
}

CaseIterable์€ enum ํƒ€์ž…์„ ์ฐธ์กฐํ• ๋•Œ ์ˆœ์„œ๋Œ€๋กœ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๋Š” ๋ฉ”์†Œ๋“œ์ด๋‹ค.

๋‹ค์Œ์€ todoManager์˜ shared์ธ๋ฐ ์‹ฑ๊ธ€ํ†ค ํŒจํ„ด์ด๋‹ค. ํ•˜๋‚˜์˜ ๋ณ€์ˆ˜๋ฅผ ์—ฌ๋Ÿฌ๊ณณ์—์„œ ๊ณต์œ ํ•ด์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ!

๋ณ€์ˆ˜ todo๋ฅผ ์ฐธ์กฐํ• ๋•Œ๋Š” todoManager์—๊ฒŒ ๋ฌผ์–ด๋ณด๊ฒŒ ์„ค๊ณ„๋˜์–ด ์žˆ๋‹ค.

์Šค์œ„ํ”„ํŠธ์˜ filter๋Š” ๋’ค์˜ ์กฐ๊ฑด์— ๋งž๋Š” ๊ฒƒ๋งŒ ์ฐพ์•„์„œ ๋ฝ‘์•„์˜ค๊ฒŒ ๋œ๋‹ค.

๋‚˜๋จธ์ง€ ํ•จ์ˆ˜๋“ค์€ manager์—๊ฒŒ์„œ ๋ฐ›์•„์˜ค๊ฒŒ๋” ์„ค๊ณ„๋˜์–ด ์žˆ๋‹ค.

 

๊ทธ๋Ÿผ manager์— ๋Œ€ํ•ด์„œ๋„ ์•Œ์•„๋ณผ๊นŒ?

class TodoManager {
    
    static let shared = TodoManager()
    
    static var lastId: Int = 0
    
    var todos: [Todo] = []
    
    func createTodo(detail: String, isToday: Bool) -> Todo {
        //TODO: create๋กœ์ง ์ถ”๊ฐ€
        let nextId = TodoManager.lastId + 1 // ์ƒˆ๋กœ์šด ์•„์ด๋””๊ฐ€ ๋˜๊ฒ ๋‹ค
        TodoManager.lastId = nextId
        return Todo(id: nextId, isDone: false, detail: detail, isToday: isToday)
    }
    
    func addTodo(_ todo: Todo) {
        //TODO: add๋กœ์ง ์ถ”๊ฐ€
        todos.append(todo)
        saveTodo()
    }
    
    func deleteTodo(_ todo: Todo) {
        //TODO: delete ๋กœ์ง ์ถ”๊ฐ€
        
        todos = todos.filter{ existingTodo in
            return existingTodo.id != todo.id
        } // filter๋Š” ์ผ์น˜ํ•˜๋Š” ๊ฒฐ๊ณผ๋ฌผ๋งŒ ๋ฐ˜ํ™˜
        saveTodo()
        
    }
    
    func updateTodo(_ todo: Todo) {
        //TODO: updatee ๋กœ์ง ์ถ”๊ฐ€
        guard let index = todos.firstIndex(of: todo) else { return } // ๋ฌธ๋ฒ• firstIndex๋Š” of: ๋กœ ์ฃผ์–ด์ง„ ๊ฐ’์ด ๊ฐ€์žฅ ์ฒซ๋ฒˆ์งธ๋กœ ๋‚˜์˜ค๋Š” ๊ฒƒ์„ ์กฐํšŒํ•œ๋‹ค
        todos[index].update(isDone: todo.isDone, detail: todo.detail, isToday: todo.isToday)
        saveTodo()
        
    }
    
    func saveTodo() {
        Storage.store(todos, to: .documents, as: "todos.json")
    }
    
    func retrieveTodo() {
        todos = Storage.retrive("todos.json", from: .documents, as: [Todo].self) ?? []
        
        let lastId = todos.last?.id ?? 0
        TodoManager.lastId = lastId
    }
}

shared๋Š” ์‹ฑ๊ธ€ํ†ค ํŒจํ„ด์œผ๋กœ ๋งค๋‹ˆ์ € ์ž์‹ ์„ ๊ฐ€๋ฅดํ‚ค๊ณ  ์žˆ๋‹ค.

lastId๋Š” ์šฐ๋ฆฌ๊ฐ€ json ํŒŒ์ผ์— ์ €์žฅํ• ๋•Œ ์•„์ด๋””๋กœ ๊ตฌ๋ถ„์ง€์„ ๊ฑด๋ฐ, ๊ทธ๋ ‡๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•œ ๋ณ€์ˆ˜์ด๋‹ค.

createTodo์‹œ ๊ตฌ์กฐ์ฒด ํ˜•ํƒœ๋กœ ๋ฆฌํ„ดํ•œ๋‹ค.

createTodo์™€ add์™€์˜ ์ฐจ์ด๋Š” ๋’ค์— ์Šคํ† ๋ฆฌ์ง€๋ฅผ ๋ณด๋ฉด์„œ ์„ค๋ช…ํ•˜์ž.

deleteTodo๋Š” filter๋ฅผ ์ด์šฉํ•˜์—ฌ ํ˜„์žฌ ์ผ์น˜ํ•˜๋Š” ๊ฒฐ๊ณผ๋งŒ ๋ฐ˜ํ™˜ํ•จ์œผ๋กœ์จ ๊ทธ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ๋‹ค.

saveTodo ๋ฐ retreieveTodo๋Š” ์Šคํ† ๋ฆฌ์ง€์— ์ €์žฅํ•˜๋Š”๋ฐ, ์ด๊ฑด ์Šคํ† ๋ฆฌ์ง€์™€ ํ•จ๊ป˜ ๋ณด๋„๋ก ํ•˜์ž.

 

โœ… Storage ํŒŒ์ผ์— ๋Œ€ํ•œ ๋ถ„์„

๊ฐ•์˜์— Storage๋ฅผ ์ˆ˜์—…์—์„œ ๋”ฐ๋กœ ์ž‘์„ฑํ•˜์ง€๋Š” ์•Š์•˜์ง€๋งŒ, ๋‚˜๋Š” ๊ทธ์ „์— ๊ณต๋ถ€๋ฅผ ํ•ด์„œ ์•Œ๊ณ  ์žˆ์—ˆ์Œ์œผ๋กœ ๋‚ด ์ง€์‹์„ ๋‹ค์‹œ ์ ๊ฒ€ํ•ด๋ณด๋Š” ๊ฒธ ๋‹ค์‹œ ํ•ด๋ณด๋ฉด์„œ ์ญ‰ ์ฝ์–ด๋ณด์ž. ํ•„์š”ํ•˜๋ฉด ๊ตฌ๊ธ€๋งํ•ด์„œ ์จ๋„ ๋ ๋“ฏ..?

public class Storage {
    
    private init() { }
    
    // TODO: directory ์„ค๋ช…
    // TODO: FileManager ์„ค๋ช…
    enum Directory {
        case documents
        case caches
        
        var url: URL {
            let path: FileManager.SearchPathDirectory
            switch self {
            case .documents:
                path = .documentDirectory
            case .caches:
                path = .cachesDirectory
            }
            return FileManager.default.urls(for: path, in: .userDomainMask).first!
        }
    }
    
    // TODO: Codable ์„ค๋ช…, JSON ํƒ€์ž… ์„ค๋ช…
    // TODO: Codable encode ์„ค๋ช…
    // TODO: Data ํƒ€์ž…์€ ํŒŒ์ผ ํ˜•ํƒœ๋กœ ์ €์žฅ ๊ฐ€๋Šฅ
    
    static func store<T: Encodable>(_ obj: T, to directory: Directory, as fileName: String) {
        let url = directory.url.appendingPathComponent(fileName, isDirectory: false)
        print("---> save to here: \(url)")
        let encoder = JSONEncoder()
        encoder.outputFormatting = .prettyPrinted
        
        do {
            let data = try encoder.encode(obj)
            if FileManager.default.fileExists(atPath: url.path) {
                try FileManager.default.removeItem(at: url)
            }
            FileManager.default.createFile(atPath: url.path, contents: data, attributes: nil)
        } catch let error {
            print("---> Failed to store msg: \(error.localizedDescription)")
        }
    }
    
    // TODO: ํŒŒ์ผ์€ Data ํƒ€์ž…ํ˜•ํƒœ๋กœ ์ฝ์„์ˆ˜ ์žˆ์Œ
    // TODO: Data ํƒ€์ž…์€ Codable decode ๊ฐ€๋Šฅ
    
    static func retrive<T: Decodable>(_ fileName: String, from directory: Directory, as type: T.Type) -> T? {
        let url = directory.url.appendingPathComponent(fileName, isDirectory: false)
        guard FileManager.default.fileExists(atPath: url.path) else { return nil }
        guard let data = FileManager.default.contents(atPath: url.path) else { return nil }
        
        let decoder = JSONDecoder()
        
        do {
            let model = try decoder.decode(type, from: data)
            return model
        } catch let error {
            print("---> Failed to decode msg: \(error.localizedDescription)")
            return nil
        }
    }
    
    static func remove(_ fileName: String, from directory: Directory) {
        let url = directory.url.appendingPathComponent(fileName, isDirectory: false)
        guard FileManager.default.fileExists(atPath: url.path) else { return }
        
        do {
            try FileManager.default.removeItem(at: url)
        } catch let error {
            print("---> Failed to remove msg: \(error.localizedDescription)")
        }
    }
    
    static func clear(_ directory: Directory) {
        let url = directory.url
        do {
            let contents = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: [])
            for content in contents {
                try FileManager.default.removeItem(at: content)
            }
        } catch {
            print("---> Failed to clear directory ms: \(error.localizedDescription)")
        }
    }
}

// MARK: TEST ์šฉ
extension Storage {
    static func saveTodo(_ obj: Todo, fileName: String) {
        let url = Directory.documents.url.appendingPathComponent(fileName, isDirectory: false)
        print("---> [TEST] save to here: \(url)")
        let encoder = JSONEncoder()
        encoder.outputFormatting = .prettyPrinted
        
        do {
            let data = try encoder.encode(obj)
            if FileManager.default.fileExists(atPath: url.path) {
                try FileManager.default.removeItem(at: url)
            }
            FileManager.default.createFile(atPath: url.path, contents: data, attributes: nil)
        } catch let error {
            print("---> Failed to store msg: \(error.localizedDescription)")
        }
    }
    
    static func restoreTodo(_ fileName: String) -> Todo? {
        let url = Directory.documents.url.appendingPathComponent(fileName, isDirectory: false)
        guard FileManager.default.fileExists(atPath: url.path) else { return nil }
        guard let data = FileManager.default.contents(atPath: url.path) else { return nil }
        
        let decoder = JSONDecoder()
        
        do {
            let model = try decoder.decode(Todo.self, from: data)
            return model
        } catch let error {
            print("---> Failed to decode msg: \(error.localizedDescription)")
            return nil
        }
    }
}

 

 

 

 

- ์ฐธ๊ณ 

https://velog.io/@dev-lena/guard-let%EA%B3%BC-if-let%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90

 

guard let๊ณผ if let์˜ ์ฐจ์ด์ 

guard let๊ณผ if let์˜ ์ฐจ์ด์ 

velog.io

https://duwjdtn11.tistory.com/520

 

[iOS] Completion Handler

Completion Handler ๋ณธ ๋ฌธ์„œ์—๋Š” ํ‰์†Œ์— ๊ณต๋ถ€๋ฅผ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•œ๋ฒˆ ์ •๋ฆฌ๊ฐ€ ํ•„์š”ํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋˜ Completion Handler ์— ๋Œ€ํ•œ ๋‚ด์šฉ์„ ๊ธฐ์žฌํ•œ๋‹ค. Prerequisite Completion Handler ๊ฐœ๋…์€ ์•Œ๋ฉด ์•Œ์ˆ˜๋ก ์–ด๋ ค์šด ๊ฐœ๋…์ด๋‹ค....

duwjdtn11.tistory.com

https://devmjun.github.io/archive/Mutating_Struct

 

Swift. ๊ตฌ์กฐ์ฒด Mutating ์ •๋ฆฌ

 

devmjun.github.io

https://www.swiftbysundell.com/articles/enum-iterations-in-swift-42/

 

Enum iterations in Swift | Swift by Sundell

With each new release, Swift keeps getting better and better at creating compiler-generated implementations of common boilerplate. One such new feature in Swift 4.2 is the new CaseIterable protocol - that enables us to tell the compiler to automatically sy

www.swiftbysundell.com

 

Comments