ViewController.swift
iOS_App/TodoList-Storyboard/TodoList-Storyboard/ViewController.swift
//
// ViewController.swift
// TodoList-Storyboard
//
// Created by Nelson Lai on 2026/7/15.
//
import UIKit
import CoreData
class ViewController: UIViewController {
// MARK: - IBOutlet
/// 與 Storyboard 中的 Table View 連結的 Outlet
@IBOutlet weak var tableView: UITableView!
// MARK: - 屬性
/// 存放所有待辦事項的陣列(資料來源)
var todoItems: [TodoItem] = []
/// Core Data 的管理物件內容(Managed Object Context),
/// 透過 AppDelegate 的 persistentContainer 取得
var context: NSManagedObjectContext {
(UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
}
/// 當清單為空時顯示的提示標籤
let emptyStateLabel: UILabel = {
let label = UILabel()
label.text = "目前沒有待辦事項\n點右上角「+」新增一筆吧!"
label.numberOfLines = 0
label.textAlignment = .center
label.textColor = .secondaryLabel
label.font = .preferredFont(forTextStyle: .body)
return label
}()
/// 用來把 Date 轉成易讀字串的日期格式器
let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .short
formatter.locale = Locale(identifier: "zh_Hant_TW")
return formatter
}()
// MARK: - 生命週期
override func viewDidLoad() {
super.viewDidLoad()
// 讓導覽列顯示大標題
navigationController?.navigationBar.prefersLargeTitles = true
// 設定空狀態提示為 Table View 的背景視圖
tableView.backgroundView = emptyStateLabel
// 從 Core Data 讀取已儲存的待辦事項
fetchTodoItems()
}
// MARK: - Core Data 操作
/// 從 Core Data 讀取所有待辦事項,並依建立時間排序(新的在前)
func fetchTodoItems() {
let request: NSFetchRequest<TodoItem> = TodoItem.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(key: "createdAt", ascending: false)]
do {
todoItems = try context.fetch(request)
} catch {
print("讀取資料失敗:\(error)")
}
// 依資料是否為空,決定要不要顯示空狀態提示
emptyStateLabel.isHidden = !todoItems.isEmpty
tableView.reloadData()
}
/// 將 context 中的變更寫入永久儲存區,並重新載入畫面
func saveAndReload() {
do {
try context.save()
} catch {
print("儲存資料失敗:\(error)")
}
fetchTodoItems()
}
// MARK: - IBAction
/// 使用者點擊右上角「+」按鈕時呼叫,跳出輸入視窗新增待辦事項
@IBAction func addButtonTapped(_ sender: UIBarButtonItem) {
let alert = UIAlertController(title: "新增待辦事項",
message: "請輸入事項內容",
preferredStyle: .alert)
// 在 Alert 中加入一個文字輸入框
alert.addTextField { textField in
textField.placeholder = "例如:買牛奶"
}
// 「新增」按鈕:取得輸入文字並建立新的 TodoItem
let addAction = UIAlertAction(title: "新增", style: .default) { [weak self] _ in
guard let self = self,
let text = alert.textFields?.first?.text?
.trimmingCharacters(in: .whitespacesAndNewlines),
!text.isEmpty else { return }
// 在 Core Data 的 context 中建立一筆新資料
let newItem = TodoItem(context: self.context)
newItem.title = text
newItem.isCompleted = false
newItem.createdAt = Date()
self.saveAndReload()
}
// 「取消」按鈕:不做任何事
let cancelAction = UIAlertAction(title: "取消", style: .cancel)
alert.addAction(addAction)
alert.addAction(cancelAction)
present(alert, animated: true)
}
// MARK: - 編輯待辦事項
/// 跳出輸入視窗,讓使用者修改既有事項的標題
func presentEditAlert(for item: TodoItem) {
let alert = UIAlertController(title: "編輯待辦事項",
message: "請修改事項內容",
preferredStyle: .alert)
alert.addTextField { textField in
textField.text = item.title
}
let saveAction = UIAlertAction(title: "儲存", style: .default) { [weak self] _ in
guard let self = self,
let text = alert.textFields?.first?.text?
.trimmingCharacters(in: .whitespacesAndNewlines),
!text.isEmpty else { return }
item.title = text
self.saveAndReload()
}
alert.addAction(saveAction)
alert.addAction(UIAlertAction(title: "取消", style: .cancel))
present(alert, animated: true)
}
}
// MARK: - UITableViewDataSource
extension ViewController: UITableViewDataSource {
/// 回傳列表的列數 = 待辦事項的數量
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return todoItems.count
}
/// 設定每一列儲存格的內容
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// 從重用佇列取出識別碼為 "TodoCell" 的儲存格
let cell = tableView.dequeueReusableCell(withIdentifier: "TodoCell",
for: indexPath)
let item = todoItems[indexPath.row]
// 已完成的事項:加上刪除線並變灰
let title = item.title ?? ""
if item.isCompleted {
let attributed = NSAttributedString(
string: title,
attributes: [
.strikethroughStyle: NSUnderlineStyle.single.rawValue,
.foregroundColor: UIColor.secondaryLabel
])
cell.textLabel?.attributedText = attributed
} else {
cell.textLabel?.attributedText = nil
cell.textLabel?.text = title
cell.textLabel?.textColor = .label
}
// 副標題顯示建立時間
if let createdAt = item.createdAt {
cell.detailTextLabel?.text = dateFormatter.string(from: createdAt)
} else {
cell.detailTextLabel?.text = nil
}
cell.detailTextLabel?.textColor = .secondaryLabel
// 已完成的事項在右側顯示勾勾
cell.accessoryType = item.isCompleted ? .checkmark : .none
return cell
}
}
// MARK: - UITableViewDelegate
extension ViewController: UITableViewDelegate {
/// 點擊某一列:切換「已完成 / 未完成」狀態
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let item = todoItems[indexPath.row]
item.isCompleted.toggle()
saveAndReload()
}
/// 向左滑動:顯示「刪除」按鈕
func tableView(_ tableView: UITableView,
trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath)
-> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(style: .destructive,
title: "刪除") { [weak self] _, _, completion in
guard let self = self else { return }
// 從 Core Data 中刪除該筆資料
let item = self.todoItems[indexPath.row]
self.context.delete(item)
self.saveAndReload()
completion(true)
}
return UISwipeActionsConfiguration(actions: [deleteAction])
}
/// 向右滑動:顯示「編輯」按鈕
func tableView(_ tableView: UITableView,
leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath)
-> UISwipeActionsConfiguration? {
let editAction = UIContextualAction(style: .normal,
title: "編輯") { [weak self] _, _, completion in
guard let self = self else { return }
self.presentEditAlert(for: self.todoItems[indexPath.row])
completion(true)
}
editAction.backgroundColor = .systemBlue
return UISwipeActionsConfiguration(actions: [editAction])
}
}
Artigos relacionados
AddItemView.swift
AddItemView.swift — swift source code from the iOS App learning materials (iOS_App/To-Do-List-Demo/To-Do-List-Demo/AddItemView.swift).
Ler artigo →ContentView.swift
ContentView.swift — swift source code from the iOS App learning materials (iOS_App/To-Do-List-Demo/To-Do-List-Demo/ContentView.swift).
Ler artigo →Item.swift
Item.swift — swift source code from the iOS App learning materials (iOS_App/To-Do-List-Demo/To-Do-List-Demo/Item.swift).
Ler artigo →ItemDetailView.swift
ItemDetailView.swift — swift source code from the iOS App learning materials (iOS_App/To-Do-List-Demo/To-Do-List-Demo/ItemDetailView.swift).
Ler artigo →To_Do_List_DemoApp.swift
To_Do_List_DemoApp.swift — swift source code from the iOS App learning materials (iOS_App/To-Do-List-Demo/To-Do-List-Demo/To_Do_List_DemoApp.swift).
Ler artigo →To_Do_List_DemoTests.swift
To_Do_List_DemoTests.swift — swift source code from the iOS App learning materials (iOS_App/To-Do-List-Demo/To-Do-List-DemoTests/To_Do_List_DemoTests.swift).
Ler artigo →