Swift UIKit待办列表问题:如何实现仅两个任务分区
解决ToDo List新增事项生成多个分区的问题
你的核心问题是数据结构与分区逻辑不匹配:todos作为二维数组,每个子数组对应一个分区,但你每次新增todo时执行todos.append([todo]),相当于给数组新增一个子数组(即新分区),再加上numberOfSections返回todos.count,导致分区数随新增不断增加。以下是针对性修改方案:
1. 初始化固定分区的数组结构
将todos初始化为两个空数组,分别对应「未完成」和「已完成」分区,同时固定分区数量为2:
class TableViewController: UITableViewController { private var arrExecuteToDo = [String]() // 索引0: 未完成(planed),索引1: 已完成(completed) private var todos = [[ToDoItem](), [ToDoItem]()] // 替换原selectedIndex,记录完整的分区+行号 private var selectedIndexPath: IndexPath? private var todoStatus: [ToDoStatus] = [.completed, .planed] // ... 其他代码不变 override func numberOfSections(in tableView: UITableView) -> Int { return 2 // 固定返回2个分区 } }
2. 修正新增Todo的逻辑
根据todo的状态,将其插入到对应的分区数组中,而非新增分区:
extension TableViewController: AddTaskVCDelegate { func didCreateToDo(todo: ToDoItem) { // 判断todo所属分区 let targetSection = todo.status == .completed ? 1 : 0 todos[targetSection].append(todo) // 仅刷新目标分区,提升性能 tableView.reloadSections(IndexSet(integer: targetSection), with: .automatic) } // ... 其他方法待修改 }
3. 修正编辑Todo的逻辑
通过完整的IndexPath记录选中位置,更新时先删除旧位置的todo,再根据新状态插入到对应分区:
// 先修改didSelectRowAt里的记录逻辑 override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { guard let secondeVC = UIStoryboard(name: Constants.mainStoryboard, bundle: .main).instantiateViewController( withIdentifier: Constants.secondVC) as? AddTaskViewController else { fatalError("Unable to Instantiate Quotes View Controller") } let todo = todos[indexPath.section][indexPath.row] secondeVC.configure(with: todo) secondeVC.type = .edit secondeVC.delegate = self navigationController?.pushViewController(secondeVC, animated: true) selectedIndexPath = indexPath // 记录完整索引路径 } // 再修改编辑回调方法 extension TableViewController: AddTaskVCDelegate { // ... didCreateToDo方法不变 func didUpdateToDo(todo: ToDoItem) { guard let oldIndexPath = selectedIndexPath else { return } // 删除旧位置的todo todos[oldIndexPath.section].remove(at: oldIndexPath.row) // 插入到新状态对应的分区 let newSection = todo.status == .completed ? 1 : 0 todos[newSection].append(todo) // 刷新涉及的两个分区 tableView.reloadSections(IndexSet([oldIndexPath.section, newSection]), with: .automatic) selectedIndexPath = nil } // ... didDeleteToDo方法待修改 }
4. 修正删除逻辑
删除操作需要针对对应分区的指定行,而非删除整个分区:
// 表格左滑删除 override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { if editingStyle == .delete { todos[indexPath.section].remove(at: indexPath.row) tableView.deleteRows(at: [indexPath], with: .automatic) } } // 协议回调删除 extension TableViewController: AddTaskVCDelegate { // ... didCreateToDo、didUpdateToDo方法不变 func didDeleteToDo() { guard let indexPath = selectedIndexPath else { return } todos[indexPath.section].remove(at: indexPath.row) tableView.deleteRows(at: [indexPath], with: .automatic) selectedIndexPath = nil } }
5. 修正Cell配置的无效代码
移除cellForRowAt里无意义的代码,确保正确获取todo数据:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: Constants.cellIndentifier, for: indexPath) as? TableViewCell else { return UITableViewCell() } // 移除无用的 _ = todos[indexPath.row] let todo = todos[indexPath.section][indexPath.row] cell.configureCell(with: todo) return cell }
可选:添加分区标题
为了区分两个分区,可实现分区标题方法:
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return section == 0 ? "未完成" : "已完成" }
完整修改后代码
import UIKit class TableViewController: UITableViewController { private var arrExecuteToDo = [String]() private var todos = [[ToDoItem](), [ToDoItem]()] // 0: 未完成, 1: 已完成 private var selectedIndexPath: IndexPath? private var todoStatus: [ToDoStatus] = [.completed, .planed] enum Constants { static let addTaskIndentifier: String = "addTaskVc" static let cellIndentifier: String = "ToDoCell" static let mainStoryboard: String = "Main" static let secondVC: String = "secondVC" } override func viewDidLoad() { super.viewDidLoad() } override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == Constants.addTaskIndentifier { let addTaskVC = segue.destination as? AddTaskViewController addTaskVC?.type = .create addTaskVC?.delegate = self } } // MARK: - Table view Data Source override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { todos[section].count } override func numberOfSections(in tableView: UITableView) -> Int { return 2 } override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return section == 0 ? "未完成" : "已完成" } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: Constants.cellIndentifier, for: indexPath) as? TableViewCell else { return UITableViewCell() } let todo = todos[indexPath.section][indexPath.row] cell.configureCell(with: todo) return cell } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { guard let secondeVC = UIStoryboard(name: Constants.mainStoryboard, bundle: .main).instantiateViewController( withIdentifier: Constants.secondVC) as? AddTaskViewController else { fatalError("Unable to Instantiate Quotes View Controller") } let todo = todos[indexPath.section][indexPath.row] secondeVC.configure(with: todo) secondeVC.type = .edit secondeVC.delegate = self navigationController?.pushViewController(secondeVC, animated: true) selectedIndexPath = indexPath } override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { if editingStyle == .delete { todos[indexPath.section].remove(at: indexPath.row) tableView.deleteRows(at: [indexPath], with: .automatic) } } } // MARK: - Extensions extension TableViewController: AddTaskVCDelegate { func didCreateToDo(todo: ToDoItem) { let targetSection = todo.status == .completed ? 1 : 0 todos[targetSection].append(todo) tableView.reloadSections(IndexSet(integer: targetSection), with: .automatic) } func didUpdateToDo(todo: ToDoItem) { guard let oldIndexPath = selectedIndexPath else { return } todos[oldIndexPath.section].remove(at: oldIndexPath.row) let newSection = todo.status == .completed ? 1 : 0 todos[newSection].append(todo) tableView.reloadSections(IndexSet([oldIndexPath.section, newSection]), with: .automatic) selectedIndexPath = nil } func didDeleteToDo() { guard let indexPath = selectedIndexPath else { return } todos[indexPath.section].remove(at: indexPath.row) tableView.deleteRows(at: [indexPath], with: .automatic) selectedIndexPath = nil } }
内容的提问来源于stack exchange,提问作者Desalutar
相关产品推荐
相关产品推荐

