添加新任务会重复 UITableView 中的现有任务

问题描述 投票:0回答:1

我正在开发一个 iOS 应用程序,用户可以使用 Core Data 添加任务。我有一个 TaskViewModel 来处理 Core Data 和 UITableView 之间的数据流。但是,我遇到了一个问题,即添加新任务会重复表视图中的现有任务。

应用流程的工作原理如下:

当用户点击“添加任务”按钮时,它会呈现一个 AddNewTaskViewController。 在 AddNewTaskViewController 中,用户输入任务名称并点击“保存”。 输入的任务然后通过 TaskViewModel 添加到 Core Data。 添加任务后,TaskViewModel 会更新其任务数组并通知 UITableView 重新加载其数据。

尽管有这些步骤,每当我添加新任务时,它都会复制表视图中的现有任务。例如,如果我添加“Test”作为第一个任务,然后添加“Test2”作为第二个任务,则表视图将显示“Test、Test、Test2”。

这是添加新任务后问题的 gif:gif of the problem

ViewController.Swift

class ViewController: UIViewController {
    let viewModel = TaskViewModel.shared
    lazy var tasksTableView: UITableView = {
        let tableView = UITableView()
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.dataSource = self
        tableView.delegate = self
        tableView.register(TaskTableViewCell.self, forCellReuseIdentifier: "TaskTableCell")
       
        tableView.estimatedRowHeight = 200
        tableView.rowHeight = UITableView.automaticDimension
        return tableView
    }()

    lazy var addNewButton: UIButton = {
        let v = UIButton()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.setTitle("Add Task", for: .normal)
        v.backgroundColor = .systemBlue
        v.layer.cornerRadius = 10
        v.frame = CGRect(x: 0, y: 0, width: 100, height: 35)

        v.addTarget(self, action: #selector(addPressed(sender: )), for: .touchUpInside)
        return v
    }()

    override func viewDidAppear(_ animated: Bool) {

        tasksTableView.reloadData()
    }


    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        view.backgroundColor = .systemBackground
        title = "Alisveris Listesi"

       navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "plus.circle.fill"), style: .done, target: self, action: #selector(showAddVC))

        navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .close,  target: self, action: #selector(deleteAllPressed(sender: )))

        setupViews()
    }
    private func setupViews() {
        view.addSubview(tasksTableView)
        configureConstraints()
    }

    private func configureConstraints() {
        NSLayoutConstraint.activate([
            tasksTableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16),
            tasksTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
            tasksTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
            tasksTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 16)
        ])
    }

    @objc func showAddVC() {
        let vc = AddNewTaskViewController()
        vc.delegate = self
        let navController = UINavigationController(rootViewController:  vc)

        navigationController?.present(navController, animated: true)
    }

    @objc func addPressed(sender: UIButton) {
        viewModel.addNewTask(name: "New Task")
    }

    @objc func getTaskPressed(sender: UIButton) {
        let tasks = CoreDataManager.shared.fetchAll()
        for task in tasks {
            print(task.name ?? "" )
        }
    }

    @objc func deleteAllPressed(sender: UIButton) {
        let tasks = CoreDataManager.shared.fetchAll()
        for task in tasks {
            CoreDataManager.shared.deleteItem(id: task.id ?? UUID() )
        }

        let fetchedTasks = CoreDataManager.shared.fetchAll()
        
        tasksTableView.reloadData()
        print(fetchedTasks.count)

    }

}


extension ViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return viewModel.numberOfRows(by: section)
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//        if indexPath.section == 0 {
//            guard let cell = tableView.dequeueReusableCell(withIdentifier: "TaskTableCell", for: indexPath) as? TaskTableViewCell else {
//                return UITableViewCell()
//            }
////            let taskSummary =
//            cell.configure(with: viewModel.tasks[indexPath.row])
//            return cell
//        }

        guard let cell = tableView.dequeueReusableCell(withIdentifier: "TaskTableCell", for: indexPath) as? TaskTableViewCell else {
            return UITableViewCell()
        }
        cell.configure(with: viewModel.tasks[indexPath.row])
        return cell
    }

    func numberOfSections(in tableView: UITableView) -> Int {
        return viewModel.numberOfTasks
    }


}

extension ViewController: ItemControllerDelegate {
    // Implement delegate methods
    func didItemAdded() {

        // Handle item added event
        tasksTableView.reloadData()
    }

    func didItemUpdated() {
        // Handle item updated event
    }
}


class TaskViewModel {
    static let shared = TaskViewModel()
    var tasks = [Task]()
    
    private init() {
        // Clear the existing tasks array
        tasks.removeAll()
        // Get all records from CoreData
        fetchAllTasks()
    }

    var numberOfTasks: Int {
        tasks.count
    }

    func fetchAllTasks() {
        // Fetch all data from core data
        tasks = CoreDataManager.shared.fetchAll().map(Task.init)
        print("Number of tasks after fetching: \(tasks.count)")
    }

    func numberOfRows(by section:Int) -> Int {
        if section == 0 {
            return 1
        }
        return numberOfTasks
    }

    func getTasksByType() -> (complete: Int, InComplete: Int) {
        let completedCount = tasks.lazy.filter({ $0.completed }).count
        let InCompletedCount = tasks.lazy.filter({ !$0.completed }).count
        
        return (completedCount,InCompletedCount)
    }

    func task(by index: Int) -> Task {
        return tasks[index]
    }

    func addNewTask(name: String) {
        let newItem = Item(context: CoreDataManager.shared.context)
        newItem.id = UUID()
        newItem.completed = false

        newItem.name = name
        newItem.createdAt = Date.now

        let newTask = Task(task: newItem)
        tasks.append(newTask)

        CoreDataManager.shared.addNewItem(item: newItem)

    }

    func toggleCompleted(task: Task) {
        CoreDataManager.shared.toggleCompleted(id: task.id)
        // call core data to toggle
        fetchAllTasks()
    }

    func deleteItem(task: Task) {
        CoreDataManager.shared.deleteItem(id: task.id)
        // call core data to delete the task
        fetchAllTasks()
    }

}

TaskViewModel.swift

class TaskViewModel {
    static let shared = TaskViewModel()
    var tasks = [Task]()
    
    private init() {
        // Clear the existing tasks array
        tasks.removeAll()
        // Get all records from CoreData
        fetchAllTasks()
    }

    var numberOfTasks: Int {
        tasks.count
    }

    func fetchAllTasks() {
        // Fetch all data from core data
        tasks = CoreDataManager.shared.fetchAll().map(Task.init)
        print("Number of tasks after fetching: \(tasks.count)")
    }

    func numberOfRows(by section:Int) -> Int {
        if section == 0 {
            return 1
        }
        return numberOfTasks
    }

    func getTasksByType() -> (complete: Int, InComplete: Int) {
        let completedCount = tasks.lazy.filter({ $0.completed }).count
        let InCompletedCount = tasks.lazy.filter({ !$0.completed }).count
        
        return (completedCount,InCompletedCount)
    }

    func task(by index: Int) -> Task {
        return tasks[index]
    }

    func addNewTask(name: String) {
        let newItem = Item(context: CoreDataManager.shared.context)
        newItem.id = UUID()
        newItem.completed = false

        newItem.name = name
        newItem.createdAt = Date.now

        let newTask = Task(task: newItem)
        tasks.append(newTask)

        CoreDataManager.shared.addNewItem(item: newItem)

    }

    func toggleCompleted(task: Task) {
        CoreDataManager.shared.toggleCompleted(id: task.id)
        // call core data to toggle
        fetchAllTasks()
    }

    func deleteItem(task: Task) {
        CoreDataManager.shared.deleteItem(id: task.id)
        // call core data to delete the task
        fetchAllTasks()
    }

}

CoreDataManager.swift

class CoreDataManager {
    static let shared = CoreDataManager()

    private init() {}

    // persistence CoreDataModel
    lazy var persistentContainer: NSPersistentContainer = {
        // Name of the CoreDataModel -- Items
        let container = NSPersistentContainer(name: "Items")
        container.loadPersistentStores { _ , error in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        }
        return container
    }()
    
    // for saving -- save, delete etc.
    var context: NSManagedObjectContext {
        persistentContainer.viewContext
    }

    func saveContext() {
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                let nserror = error as NSError
                print("Error - saveContext:", nserror.description, nserror.userInfo)
            }
        }
    }

    // Fetch all Items from CoreData
    func fetchAll() -> [Item] {
        var tasks = [Item]()
        
        // EntityName.fetchRequest()
        let fetchRequest: NSFetchRequest<Item> = Item.fetchRequest()
        let sortByCreatedDate = NSSortDescriptor(key: "createdAt", ascending: true)

        fetchRequest.sortDescriptors = [sortByCreatedDate]

        do {
            tasks = try context.fetch(fetchRequest)
        } catch {
            let nserror = error as NSError
            print("Error - fetchAll:", nserror.description, nserror.userInfo)
        }

        return tasks
    }

    // Add a item to CoreData
    func addNewItem(item: Item) {
        // create a new item if this item doesn't exist

        // save changes
        saveContext()
    }
    
    // Toggle Completed from CoreData
    func toggleCompleted(id: UUID) {
        let fetchRequest: NSFetchRequest<Item> = Item.fetchRequest()
        let predicate = NSPredicate(format: "id=%@", id.uuidString)
        fetchRequest.predicate = predicate

        do {
            if let fetchedTasks = try context.fetch(fetchRequest).first(where: { foundItem in
                foundItem.id == id
            }) {
                fetchedTasks.completed = !fetchedTasks.completed
            }

            // Save Core Data
            saveContext()

        } catch let error as NSError {
            print("toggleCompleted Error: \(error) \(error.userInfo)")
        }
    }

    // Delete item from CoreData
    func deleteItem(id: UUID) {
        let fetchRequest: NSFetchRequest<Item> = Item.fetchRequest()

        let predicate = NSPredicate(format: "id=%@", id.uuidString)
        fetchRequest.predicate = predicate

        do {
            let fetchedTasks = try context.fetch(fetchRequest)

            for task in fetchedTasks {
                context.delete(task)
            }

            // Save Core Data
            saveContext()

        } catch let error as NSError {
            print("deleteItem Error: \(error) \(error.userInfo)")
        }
    }

}

添加NewTaskViewController.swift

class AddNewTaskViewController: UIViewController {
    weak var delegate: ItemControllerDelegate?

    lazy var taskNameLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.text = "Task Name"
        return label
    }()

    lazy var taskNameTextField: UITextField = {
        let textField = UITextField()
        textField.translatesAutoresizingMaskIntoConstraints = false
        textField.placeholder = "Enter Task Name"
        textField.borderStyle = .roundedRect
        return textField
    }()

    let viewModel = TaskViewModel.shared

    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupView()
    }

    private func setupView() {
        view.backgroundColor = .systemBackground
        title = "Add New Task"
        navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Save", style: .done, target: self, action: #selector(savePressed))

        view.addSubview(taskNameLabel)
        view.addSubview(taskNameTextField)
        setupConstraints()
    }

    private func setupConstraints() {
        NSLayoutConstraint.activate([
            taskNameLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 15),
            taskNameLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 15),
            taskNameLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -15),

            taskNameTextField.topAnchor.constraint(equalTo: taskNameLabel.bottomAnchor, constant: 15),
            taskNameTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 15),
            taskNameTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -15),
        ])
    }

    // Action Function
    @objc func savePressed() {
        guard let name = taskNameTextField.text else { 
            let alert = UIAlertController(title: "Error", message: "Name can't be empty", preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK", style: .default))
            present(alert, animated: true)
            return }

        viewModel.addNewTask(name: name)

        delegate?.didItemAdded()

        navigationController?.dismiss(animated: true)
    }
}

我尝试在从 Core Data 获取任务之前清除任务数组,但问题仍然存在。我究竟做错了什么?如何防止表视图中的任务重复?

任何帮助或见解将不胜感激!预先感谢您。

ios swift uitableview mobile
1个回答
0
投票

在委托方法中

numberOfSections(in:)
你返回的总行数一定是错误的

func numberOfSections(in tableView: UITableView) -> Int {
    return viewModel.numberOfTasks
}

查看视图模型中的函数

numberOfRows(by:)
,我假设部分的数量是两个,因此将委托方法更改为

func numberOfSections(in tableView: UITableView) -> Int {
    return 2
}
© www.soinside.com 2019 - 2024. All rights reserved.