iOS ์ ๋ฌธ ๊ฐ์๋ฅผ ์๊ฐํ๊ณ ๋ฐฐ์ด ๋ด์ฉ์ ํ์ฉํ์ฌ ToDoList App์ ๋ง๋๋ ๊ฒ์ด ๊ณผ์ ์ด๋ค!
์ด์ ์ซ์ ์ผ๊ตฌ ๊ณผ์ ์ ๋์ผํ๊ฒ ๊ตฌํํด์ผ ๋๋ ๊ธฐ๋ฅ์ผ๋ก ํ์ ๊ตฌํ ๊ธฐ๋ฅ
์ด ์๊ณ , ๋ถ๊ฐ์ ์ผ๋ก ์ ํ ๊ตฌํ ๊ธฐ๋ฅ
์ด ์๋ค.
ํ์ ๊ตฌํ ๊ธฐ๋ฅ
Storyboard๋ฅผ ํ์ฉํ์ฌ UI๋ฅผ ๊ตฌ์ฑํด์ฃผ์ธ์.
UIButton๊ณผ UITableView์ ํ์ฉํ์ฌ ํ๋ฉด์ ๊ตฌ์ฑํด์ฃผ์ธ์.
ํ ์ผ์ ๋ํ ๋ฐ์ดํฐ ๊ตฌ์ฑ
- ํ ์ผ ๋ฐ์ดํฐ์ ๊ณ ์ ๊ฐ์ธ โid (Int)โ
- ํ ์ผ ์ ๋ชฉ์ธ โTitle (String)โ
- ์๋ฃ ์ฌ๋ถ๋ฅผ ํ์ธํ โisCompleted (Bool)โ
Lv1์์ ๋ง๋ Todo ์ถ๊ฐ ๋ฒํผ์ ์ด์ฉํด์ฃผ์ธ์.
UIAlertController๋ฅผ ํ์ฉํด์ ํ ์ผ ์ถ๊ฐ UI ๋ฐ ๊ธฐ๋ฅ์ ๊ตฌํํด๋ณด์ธ์.
(Closure ๋ฑ์ ์ฌ์ฉํด์ Action์ ์ถ๊ฐํด๋ณด์ธ์.)
๋ฒํผ ํน์ UISegmentedControl์ ํ์ฉ, Todo์ ์๋ฃ ์ํ๋ฅผ ์๋ฃ/๋ฏธ์๋ฃ ์ํ๋ก ๋ณ๊ฒฝํฉ๋๋ค.
Todo List์์ ํน์ Todo๋ฅผ ์ญ์ ํ ์ ์๋๋ก ํ๋ฉด๊ณผ ๊ธฐ๋ฅ์ ์์ ๋กญ๊ฒ ๊ตฌ์ฑํด๋ณด์ธ์.
์ ํ ๊ตฌํ ๊ธฐ๋ฅ
Todo ๋ฐ์ดํฐ์ ๋ค์ด์๋ ๋ค์ํ ์์๋ค์ ๋ฐฐ์นํด๋ด
๋๋ค.
Interface Builder์ ์๋ง์ ์์ฑ๋ค์ ๋ฐ๊พธ์ด๋ณด์ธ์. (์ฝ๋๋ก ๋ฐ๊พธ์ด๋ณด์๋ ์ข์์)
iOS์์๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๊ตฌํํด์ฃผ๋ animation๋ค์ด ๋ค์ํ๊ฒ ์์ด์
ex) tableview์ cell์ด ์ถ๊ฐ๋๊ฑฐ๋ ์ญ์ ๋๋ animation
์ ์กฐ๊ฑด๋ค ์ค ํ์ฌ๋ ํ์ ๊ตฌํ ๊ธฐ๋ฅ์ธ Lv.3๊น์ง ์ค๊ณํ์๊ณ , Lv4์ Lv5์ ๊ตฌํ ๋ชฉํ ์ค ์ผ๋ถ๋ง ๊ตฌํ์ ์ฑ๊ณตํ์๋ค... ๐ฅฒ
ํ์ ๊ตฌํ ๊ธฐ๋ฅ
Storyboard๋ฅผ ํ์ฉํ์ฌ UI๋ฅผ ๊ตฌ์ฑ
UIButton๊ณผ UITableView์ ํ์ฉํ์ฌ ํ๋ฉด์ ๊ตฌ์ฑ
ํ ์ผ์ ๋ํ ๋ฐ์ดํฐ ๊ตฌ์ฑ
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var toDoListTable: UITableView!
var data: [(id: Int, title: String, isCompleted: Bool)] = []
var count: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
toDoListTable.dataSource = self
toDoListTable.delegate = self
toDoListTable.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
}
@IBAction func addListButton(_ sender: Any) {
count += 1
data.append((id: count, title: "todolist title", isCompleted: false))
toDoListTable.reloadData()
}
}
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let item = data[indexPath.row]
cell.textLabel?.text = item.title
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Selected: \(data[indexPath.row])")
tableView.deselectRow(at: indexPath, animated: true)
}
}
์ฐ์ ์๋์ ๊ทธ๋ฆผ์ฒ๋ผ ViewController์ UI๋ฅผ ๊ตฌ์ฑํ์๋ค!
ํด๋น ๊ณผ์ ์์ UITableView๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์, ๋ค์ ๋จ๊ณ์์ ์ฝ๋๊ฐ ๊ธธ์ด๊ธธ ๊ฒ์ ๊ณ ๋ คํ์ฌ extension
์ ํ์ฉํ์ฌ UITableViewDataSource
์ UITableViewDelegate
์ ๋ํ ์ค์ ์ ํด์ฃผ์๋ค.
์ด๋, Cell์ ์
๋ ฅ๋ ๋ฐ์ดํฐ๋ ViewController
ํด๋์ค ๋ด๋ถ์ data
๋ฐฐ์ด์ ๋ง๋ค์ด์ ํ์ฉํ์๋ค.
ํด๋น ๋ฐฐ์ด์ ์ถ๋ ฅ๋ ๊ฐ์ ๊ณ ์ ๋ฒํธ, ์ถ๋ ฅ๋ ๋ด์ฉ๊ณผ ์๋ฃ ์ฌ๋ถ๋ฅผ ํฌํจ์์ผฐ๋ค.
์ฌ๊ธฐ๊น์ง๊ฐ Lv.1 ๋จ๊ณ์ด์ง๋ง, addListButton
ํจ์๋ฅผ ๋ง๋ค์ด ๋ฒํผ์ ๋๋ฅด๋ฉด, count
๊ฐ ์ฆ๊ฐํ์ฌ ๊ณ ์ ๋ฒํธ๊ฐ ์๋ง๊ฒ ์
๋ ฅ๋๋๋ก ์ค์ ํ์๋ค.
ํ์ ๊ตฌํ ๊ธฐ๋ฅ
Lv.1์์ ๋ง๋ Todo ์ถ๊ฐ ๋ฒํผ์ด ๋์ํ๋๋ก ๊ตฌํ
UIAlertController๋ฅผ ํ์ฉํด์ ํ ์ผ ์ถ๊ฐ UI ๋ฐ ๊ธฐ๋ฅ์ ๊ตฌํ
UISegmentedControl์ ํ์ฉ, Todo์ ์๋ฃ ์ํ๋ฅผ ์๋ฃ/๋ฏธ์๋ฃ ์ํ๋ก ๋ณ๊ฒฝ
ํ ์ผ์ ๋ํ๋ด๋ Todo์ ์๋ฃ/๋ฏธ์๋ฃ ์ํ์ ๋ฐ๋ผ UI๋ฅผ ๋ณ๊ฒฝ
:
@IBAction func addListButton(_ sender: Any) {
let alertTitle = "ํ ์ผ ์ถ๊ฐ"
let addTitle = "์ถ๊ฐ"
let cancelTitle = "์ทจ์"
let addListAlert = UIAlertController(title: alertTitle, message: nil, preferredStyle: .alert)
addListAlert.addTextField { textField in
textField.placeholder = "ํ ์ผ ์
๋ ฅ"
}
let cancelButton = UIAlertAction(title: cancelTitle, style: .cancel)
let addButton = UIAlertAction(title: addTitle, style: .default) { _ in
if let textField = addListAlert.textFields?.first, let taskTitle = textField.text, !taskTitle.isEmpty {
self.addTask(title: taskTitle)
}
}
addListAlert.addAction(addButton)
addListAlert.addAction(cancelButton)
self.present(addListAlert, animated: true)
}
func addTask(title: String) {
count += 1
data.append((id: count, title: title, isCompleted: false))
toDoListTable.reloadData()
}
}
:
extension ViewController: UITableViewDataSource, UITableViewDelegate {
:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let item = data[indexPath.row]
cell.textLabel?.text = item.title
let taskStateSwitch = UISwitch()
taskStateSwitch.isOn = item.isCompleted
taskStateSwitch.addTarget(self, action: #selector(switchStateChanged(_:)), for: .valueChanged)
cell.accessoryView = taskStateSwitch
return cell
}
:
@objc func switchStateChanged(_ sender: UISwitch) {
guard let cell = sender.superview as? UITableViewCell,
let indexPath = toDoListTable.indexPath(for: cell),
let taskTitle = cell.textLabel?.text
else {
return
}
data[indexPath.row].isCompleted = sender.isOn
let attributedString = NSMutableAttributedString(string: taskTitle)
if sender.isOn {
attributedString.addAttribute(NSAttributedString.Key.strikethroughStyle, value: 1, range: NSMakeRange(0, attributedString.length))
}
cell.textLabel?.attributedText = attributedString
}
}
์ฝ๋ ์ ๋ถ๋ฅผ ์ ๋ ฅํ๊ธฐ์ ์์ด ๋๋ฌด ๋ง์ ์์ ๋ฐ ์ถ๊ฐ๋ ๋ถ๋ถ๋ง ์ ๋ฆฌํ์๋ค!
Lv.2 ์๊ตฌ ์กฐ๊ฑด์ ๋ฐ๋ฅด๊ธฐ ์ํด addListButton
์ UIAlertController
๋ฅผ ์ถ๊ฐํ์์ผ๋ฉฐ, UIAlertAction
์ ํ์ฉํ์ฌ "์ถ๊ฐ" ๋ฒํผ์ ๋๋ฅผ ๊ฒฝ์ฐ TextField์ ์
๋ ฅํ ๊ฐ์ addTask
ํจ์์ title
์ ์ ์ฅํ๋๋ก ๋ง๋ค์๋ค.
์ด๋,
textField
์ ์ ๋ ฅ๋ ๊ฐ์ ๋ฐฐ์ด ํํ๋ก ์ ์ฅ๋๊ธฐ์.first
๋ฅผ ํตํด ์ ๋ ฅ๊ฐ์ ์ ๊ทผํ ์ ์๋ค!
๊ทธ๋ฆฌ๊ณ UISwitch
๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ Cell ๋ง๋ค ์๋ฃ ์ฌ๋ถ๋ฅผ ์ ํํ ์ ์๋ ๊ธฐ๋ฅ์ ์ถ๊ฐํ์๊ณ , ์ด๋ฅผ ๊ตฌํํ๊ธฐ ์ํด switchStateChanged
ํจ์๋ฅผ ๋ง๋ค์๋ค.
๋ํ ํ ์ผ์ ์๋ฃํ์ ๊ฒฝ์ฐ, ํด๋น Tilte์ ๋ฐ์ค์ด ์๊ธฐ๋๋ก NSMutableAttributedString
์ ํ์ฉํ์๋ค. ํด๋น ๊ธฐ๋ฅ๋ switchStateChanged
ํจ์ ๋ด๋ถ์ ์๋๋ฐ, sender.isOn
๋ฅผ ํตํด ์๋ฃ ์ฌ๋ถ๋ฅผ ํ์
ํ๊ณ ํด๋น ๋์์ ํ๋๋ก ์ค๊ณํ์๋ค.
ํ์ ๊ตฌํ ๊ธฐ๋ฅ
Todo ์ญ์ ํ๊ธฐ ๊ธฐ๋ฅ ์ถ๊ฐ
Todo๋ฅผ ์ค์์ดํํ์ฌ ์ญ์ - UITableView์ ๊ธฐ๋ฅ
:
extension ViewController: UITableViewDataSource, UITableViewDelegate {
:
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
data.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
:
}
Lv.3์์๋ ์ญ์ ๊ธฐ๋ฅ์ ์ถ๊ฐํด์ผ ํ๋ฉฐ, ํด๋น ๊ธฐ๋ฅ์ ์ํด editingStyle == .delete
์กฐ๊ฑด์ ํ์ฉํ์ฌ tableView ๋ด์ Cell์ ์ญ์ ์ํค๋๋ก ๋ง๋ค์๋ค.
์ ํ ๊ตฌํ ๊ธฐ๋ฅ
Todo ๋ฐ์ดํฐ์ ๋ค์ด์๋ ๋ค์ํ ์์๋ค์ ๋ฐฐ์น
Interface Builder์ ์๋ง์ ์์ฑ๋ค์ ์์
ํ ์ผ ์ถ๊ฐ ๋ฑ animation์ด ์๋ ์ฝ๋ ๊ตฌ์ฑ
๊ธฐํ ์๋ํด๋ณด๊ณ ์ถ์ ๊ธฐ๋ฅ
import UIKit
class ViewController: UIViewController {
var data: [(id: Int, title: String, isCompleted: Bool, addTime: String)] = []
:
func addTask(title: String) {
let addListTime = DateFormatter()
addListTime.dateFormat = "HH:mm"
let addListTimeTrnasString = addListTime.string(from: Date())
count += 1
data.append((id: count, title: title, isCompleted: false, addTime: addListTimeTrnasString))
toDoListTable.reloadData()
}
}
extension ViewController: UITableViewDataSource, UITableViewDelegate {
:
@objc func switchStateChanged(_ sender: UISwitch) {
:
showcompletedAlert()
}
:
}
func showcompletedAlert() {
let completedTitle = "๐๐๐"
let completedMessage = "ํ ์ผ์ ๋๋์ต๋๋ค! ๐"
let doneTitle = "ํ์ธ"
let completedAlert = UIAlertController(title: completedTitle, message: completedMessage, preferredStyle: .alert)
let doneButton = UIAlertAction(title: doneTitle, style: .default) { _ in
}
completedAlert.addAction(doneButton)
self.present(completedAlert, animated: true)
}
}
๋ง์ง๋ง Lv.4 & 5์์๋ ์๊ตฌํ๋ ๋ชจ๋ ๊ธฐ๋ฅ์ ๊ตฌํํ์ง ๋ชปํ์์ง๋ง, ๋ช๋ช ๊ธฐ๋ฅ์ ๊ตฌํํ์๊ธฐ์ ์ด๋ฅผ ์ ๋ฆฌํด๋ณด์๋ค!
์ฐ์ ๊ธฐ์กด data์ addTime
์ด๋ผ๋ "์์ฑ ์๊ฐ"์ด๋ผ๋ ์์๋ฅผ ์ถ๊ฐํ์๋ค. ๊ทธ๋์ addTask
๋ฅผ ์ดํด๋ณด๋ฉด DateFormatter
๋ฅผ ํตํด ์๊ฐ์ ๋ถ๋ฌ์ .dateFormat = "HH:mm"
์ผ๋ก 24์๊ฐ ํํ๋ก ์๊ฐ์ ๋ณํํด์ค ๋ค, ์ด๋ฅผ addListTimeTrnasString
์ ๋ฌธ์์ด ํ์
์ผ๋ก ์ ์ฅํด์ฃผ์๋ค.
๊ทธ๋ฆฌ๊ณ ํ ์ผ์ ์๋ฃํ์ ๋, showcompletedAlert
ํจ์๋ฅผ ํตํด ๐
์ถํ ์๋์ด ๋ฐ์ํ๋๋ก ๋ง๋ค์๋ค. ๐
์๋๋ todoList App์ ์คํ์ํจ ๋ชจ์ต์ด๋ค!
์ด๋ฒ ๊ณผ์ ๋ ์ด๋ป๊ฒ ๋ง๋ค๋ค๋ณด๋ ์์ฑ์ ํ ๊ฒ ๊ฐ๋ค.๐
๊ทธ๋๋ง ์ ๋ฒ ๊ณผ์ ์ ๋นํด ์ฝ๋๋ฅผ ์ฝ๊ธฐ ํธํ ๊ฒ ๊ฐ๊ณ ๋ฌด์๋ณด๋ค ๋ณ์๋ ํจ์ ๋ช ์ ์ ์ ํ ์ ์์ฑํ ๊ฒ ๊ฐ์ ๊ฐ๋ ์ฑ์ด ํจ์ฌ ์ข์์ง ๊ฒ์ฒ๋ผ ๋๊ปด์ง๋ค!
ํ์ง๋ง ๋์ด๋๊ฐ ์ง๋๋ฒ ๊ณผ์ ์ ๋นํด ์๋์ ์ผ๋ก ๋งค์ฐ ์ด๋ ค์ ์ผ๋ฉฐ, ์ด๋ก ์ธํด ์ค์ค๋ก ํด๊ฒฐํ๋ค๋ณด๋ค๋ ๊ฑฐ์ ๋๋ถ๋ถ์ ๊ธฐ๋ฅ์ ๊ฒ์์ ํตํด ๋ง๋ค์๋ค... ๐
์ด๋ฌํ ๋ถ๋ถ์ด ๋ง์ด ์์ฝ๊ฒ ๋๊ปด์ง๋ค.
๊ทธ๋๋ ์ง๊ธ์ฒ๋ผ ๋ชจ๋ฅด๋ ๋ด์ฉ์ ์ ํ์ฉํด์ ๊ฒฐ๊ณผ๋ฌผ์ ๋ง๋ค์ด ๋ด๋ ๊ฒ๋ ๋๋ฆ ์๋ฏธ๊ฐ ์์๋ ๊ฒ ๊ฐ๋ค!
Github -> toDoList-Memo