Alan Weng
Alan Weng

Reputation: 656

tableviewdeleterows crashing app even though data was updated

I currently have this block of code that should delete a table row.

extension HomeController: SwipeTableViewCellDelegate {
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
    guard orientation == .right else { return nil }
    print("in delete")
    let deleteAction = SwipeAction(style: .destructive, title: "Delete") { [self] action, indexPath in
        print("currently deleting")
        userHabitData.remove(at: indexPath.section)
        tableView.deleteRows(at: [indexPath], with: .fade)
    }

    // customize the action appearance
    deleteAction.image = UIImage(named: "delete")

    return [deleteAction]
}

I made sure to change my data source before deleting the row with

userHabitData.remove(at: indexPath.section)

However the code still throws this error

Thread 1: "Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out)."

Here is my complete view controller

//
//  ViewController.swift
//  Habit Tracker
//
//  Created by Alan Weng on 11/24/20.
//

import UIKit
import SwipeCellKit

class tableViewCell: SwipeTableViewCell {
    @IBOutlet weak var habitName: UILabel!
    @IBOutlet weak var habitCount: UILabel!
    
    
    var habit: Habit? {
        didSet {
            self.updateUI()
        }
    }
    
    func updateUI() {
        print("being run")
        habitName?.text = habit?.title
        habitCount?.text = habit?.detail
//        progressLabel?.observedProgress = habit?.progress
    }
    
}

class HomeController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    @IBOutlet weak var deno_label: UILabel!
    @IBOutlet weak var tableView: UITableView!
    let impactFeedbackgenerator = UIImpactFeedbackGenerator(style: .heavy)
    @IBOutlet weak var deno_img: UIImageView!
    var UserHabitDict: [String:String] = [:]
    var userHabitData = [HabitDict]()
    var userHabitName: String?
    var userHabitCount: String?
    let cellReuseID = "habitName"
    let cellSpacingHeight: CGFloat = 15
    let customRed = UIColor().customRed()
    let customBlue = UIColor().customBlue()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.tableView.delegate = self
        self.tableView.dataSource = self
    }

    @IBAction func createNewGoal(_ sender: Any) {
        let goalVC = storyboard?.instantiateViewController(withIdentifier: cellReuseID) as! CreateGoalController
        impactFeedbackgenerator.prepare()
        impactFeedbackgenerator.impactOccurred()
        goalVC.habitDelegate = self
        present(goalVC, animated: true, completion: nil)
    }
    
    // Think of how ios settings have different sections w diff spacings
    func numberOfSections(in tableView: UITableView) -> Int {
        return UserHabitDict.count
    }
    
    // Adjusts cell spacing between habits
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return cellSpacingHeight
    }
    
    // Adjust row height
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 75
    }
    
    // Allows the table to keep expanding based on how many habits are in the array
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1
    }
    
    // Make the background color show through
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let headerView = UIView()
        headerView.backgroundColor = UIColor.clear
        return headerView
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if habitIsEmpty() {
            deno_img.alpha = 0.0
            deno_label.alpha = 0.0
        }
        print("making habit")
        let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseID) as! tableViewCell
        let dictKey = userHabitData[indexPath.section].getName()
        let dictValue = userHabitData[indexPath.section].getCount()
        cell.delegate = self
        cell.backgroundColor = customBlue
        cell.layer.cornerRadius = 10
        cell.habit = Habit(title: dictKey, detail: dictValue)
        print(userHabitData)
        
        return cell
    }
    
    func tableView(_ tableView: UITableView, editActionsOptionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> SwipeOptions {
        var options = SwipeOptions()
        options.expansionStyle = .destructive
        options.transitionStyle = .border
        return options
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        // note that indexPath.section is used rather than indexPath.row
//        print("You tapped cell number \(indexPath.section).")
//        let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseID) as! tableViewCell
//      cell.habit?.updateDetail()
//      tableView.reloadData()
//        let dictKey = userHabitData[indexPath.section].getName()
//        var dictValue = userHabitData[indexPath.section].getCount()
//        print("over here")
//        dictValue = String(Int(dictValue)! - 1)
//        print(dictValue)
//        cell.habit = Habit(title: dictKey, detail: dictValue)
//        tableView.reloadData()
        
    }
    
    func allowMultipleLines(tableViewCell: UITableViewCell) {
        tableViewCell.textLabel?.lineBreakMode = .byWordWrapping
    }
    
    func habitIsEmpty() -> Bool {
        if userHabitData.isEmpty {
            return false
        } else {
            return true
        }
    }
    
}

extension HomeController: CreateGoalDelegate {
    func didTapSave(name: String, count: String) {
        userHabitName = name
        userHabitCount = count
        UserHabitDict[name] = count
        userHabitData.append(HabitDict(habitName: name, habitCount: count))
//      tableView.insertRows(at: [IndexPath(row: userHabitData.count, section: 0)], with: .automatic)
        tableView.reloadData()
        print("in reload data")
    }
}

extension UIColor {
    func customRed() -> UIColor {
        return UIColor(red: 0.607, green: 0.160, blue: 0.282, alpha: 1.00)
    }
    func customBlue() -> UIColor {
        return UIColor(red: 0.509, green: 0.701, blue: 0.964, alpha: 1.00)
    }
}

struct HabitDict {
    let habitName: String
    let habitCount: String
    
    func getName() -> String {
        return habitName
    }
    
    func getCount() -> String {
        return habitCount
    }
}

extension HomeController: SwipeTableViewCellDelegate {
    func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
        guard orientation == .right else { return nil }
        print("in delete")
        let deleteAction = SwipeAction(style: .destructive, title: "Delete") { [self] action, indexPath in
            print("currently deleting")
            userHabitData.remove(at: indexPath.section)
            tableView.deleteRows(at: [indexPath], with: .fade)
        }

        // customize the action appearance
        deleteAction.image = UIImage(named: "delete")

        return [deleteAction]
    }
}

any help would be greatly appreciated!!

Upvotes: 0

Views: 78

Answers (1)

vadian
vadian

Reputation: 285082

The problem is that your data source is inconsistent.

In the delete method the data source array is userHabitData however in numberOfSections you are using UserHabitDict which is not being updated in the delete action.

Replace numberOfSections with

func numberOfSections(in tableView: UITableView) -> Int {
    return userHabitData.count
}

and then delete the section (including the row)

let deleteAction = SwipeAction(style: .destructive, title: "Delete") { [self] action, indexPath in
    print("currently deleting")
    userHabitData.remove(at: indexPath.section)
    tableView.deleteSections([indexPath.section], with: .fade)
}

And please remove the getName and getCount functions in the struct. They are redundant. Use the name and count members directly. And you could remove also the redundant information Habit from the names

Upvotes: 1

Related Questions