You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 23:25:00