简体   繁体   中英

Remove specific array element by cell text string value [swift 4]

I have two arrays for my UITableView . One holds the array items and the other holds the value of the array items in case they have a checkmark on them. I am having a problem now because my two arrays don't have the same IndexPath . I need something to delete the item in my selectedChecklist array by its string value. How can I do that?

func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
    if editingStyle == .delete {
        checklist.remove(at: indexPath.row)
        selectedChecklist.removeAll { $0 == String(cell.textLabel) }
        myTableView.reloadData()
    }
}

printed selectedChecklist

["Test", "Test2", "Test3", "Asdf", "Test2", "Test2", "Test"]

Here is my code for the whole array. I am struggling implementing the answers:

import UIKit

class ChecklistViewController: BaseViewController, UITableViewDelegate, UITableViewDataSource{

var dataHolder = [ListItem]()

var newChecklistItemString: String?
var alertInputTextField: UITextField?

@IBOutlet weak var myTableView: UITableView!

let mainStoryboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)

var checkedItems: [ListItem] {
    return dataHolder.filter { return $0.isChecked }
}
var uncheckedItems: [ListItem]  {
    return dataHolder.filter { return !$0.isChecked }
}

public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

    return (dataHolder.count)
}

public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let cell = UITableViewCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: "cell")
    cell.textLabel?.font = UIFont.boldSystemFont(ofSize: 18.0)
    cell.textLabel?.text = dataHolder[indexPath.row].title

    return cell
}

// checkmarks when tapped
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

    if (tableView.cellForRow(at: indexPath)?.accessoryType != .checkmark) {
        tableView.cellForRow(at: indexPath)?.accessoryType = .checkmark
    }else {
        tableView.cellForRow(at: indexPath)?.accessoryType = .none
    }

    tableView.deselectRow(at: indexPath, animated: true)
    saveDefaults()
}

func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
    if editingStyle == .delete {
        checkedItems[indexPath.row].isChecked = false
        myTableView.reloadData()
    }
}


override func viewDidAppear(_ animated: Bool) {
    myTableView.reloadData()
}

override func viewDidLoad() {
    super.viewDidLoad()

    addSlideMenuButton()

    loadDefaults()
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
}

@IBAction func addNewObject(_ sender: Any) {

    let alert = UIAlertController(title: "New Item", message: nil, preferredStyle: .alert)
    alert.addTextField { (alertInputTextField) in
        alertInputTextField.autocapitalizationType = .sentences
    }

    alert.addAction(UIAlertAction(title: "Cancel", style: .default, handler: { (action) in
        self.dismiss(animated: true, completion: nil)
    }))

    alert.addAction(UIAlertAction(title: "Add", style: .default, handler: { (action) in

        let textf = alert.textFields![0] as UITextField

        let indexPath = IndexPath(row: self.dataHolder.count, section: 0)
        self.dataHolder.append(ListItem(title: textf.text!, isChecked: false))
        self.saveDefaults()
        self.myTableView.insertRows(at: [indexPath], with: .automatic)

    }))

    self.present(alert, animated: true, completion: nil)

}

func loadDefaults()
{
    self.dataHolder = UserDefaults.standard.array(forKey: "dataHolder") as? [ListItem] ?? []

}

func saveDefaults()
{
    UserDefaults.standard.set(self.dataHolder, forKey: "dataHolder")

}
}

class ListItem {

var title: String
var isChecked: Bool

init(title: String, isChecked: Bool) {
    self.title = title
    self.isChecked = isChecked
}
}

You code is too complicated. As you are using a class as data source the extra arrays are redundant.

  • Remove checkedItems and uncheckedItems

    var checkedItems: [ListItem] { return dataHolder.filter { return $0.isChecked } } var uncheckedItems: [ListItem] { return dataHolder.filter { return !$0.isChecked } }

  • In cellForRow set the checkmark according to isChecked and reuse cells!

     public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) cell.textLabel?.font = UIFont.boldSystemFont(ofSize: 18.0) // better set this in Interface Builder let data = dataHolder[indexPath.row] cell.textLabel?.text = data.title cell.accessoryType = data.isChecked ? .checkmark : .none return cell } 
  • in didSelectRowAt toggle isChecked in the model and update only the particular row

     func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { dataHolder[indexPath.row].isChecked.toggle() tableView.reloadRows(at: [indexPath], with: .none) tableView.deselectRow(at: indexPath, animated: true) saveDefaults() } 
  • In tableView:commit:forRowAt: delete the row at the given indexPath

     func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { if editingStyle == .delete { dataHolder.remove(at: indexPath.row) myTableView.deleteRows(at: [indexPath], with: .fade) saveDefaults() } } 
  • And you cannot save an array of a custom class to UserDefaults . I recommend to use a struct and Codable

     struct ListItem : Codable { var title: String var isChecked: Bool } func loadDefaults() { guard let data = UserDefaults.standard.data(forKey: "dataHolder") else { self.dataHolder = [] return } do { self.dataHolder = try JSONDecoder().decode([ListItem].self, for: data) } catch { print(error) self.dataHolder = [] } } func saveDefaults() { do { let data = try JSONEncoder().encode(self.dataHolder) UserDefaults.standard.set(data, forKey: "dataHolder") } catch { print(error) } } 

Avoid using 2 array to "persist" your models. Instead you can generate a single Array with tuples :

var myArray: [(String, Bool)] = [("Test", false), ("Test1", false), ("Test2", false)]

Starting here the problem is simplified, and you will not have index path issue again

Edit

I've changed my code to support [ListItem] saving to UserDefaults - that comment brought by Leo Dabus I also changed a couple of lines that were inspired by vadian 's code who appear to have a great coding style.

class ChecklistViewController: BaseViewController, UITableViewDelegate, UITableViewDataSource{

    var dataHolder: [ListItem] = DefaultsHelper.savedItems

    @IBOutlet weak var myTableView: UITableView!


    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataHolder.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = UITableViewCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: "cell")

        cell.textLabel?.font = UIFont.boldSystemFont(ofSize: 18.0)

        let currentListItem = dataHolder[indexPath.row]

        cell.textLabel?.text = currentListItem.title
        cell.accessoryType = currentListItem.isChecked ? .checkmark : .none

        return cell
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

        dataHolder[indexPath.row].isChecked.toggle()
        DefaultsHelper.saveItems(items: dataHolder)

        tableView.reloadRows(at: [indexPath], with: .none)
        tableView.deselectRow(at: indexPath, animated: true)
    }

    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {

        if editingStyle == .delete {

            dataHolder.remove(at: indexPath.row)
            DefaultsHelper.saveItems(items: dataHolder)

            myTableView.reloadData()
            myTableView.deleteRows(at: [indexPath], with: .automatic)
        }
    }


    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        myTableView.reloadData()
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        // be sure you've set your tableView's dataSource and delegate to this class (It's fine if you've handled this on the storyboard side)

        addSlideMenuButton()
    }

    @IBAction func addNewObject(_ sender: Any) {

        let alert = UIAlertController(title: "New Item", message: nil, preferredStyle: .alert)
        alert.addTextField { (alertInputTextField) in
            alertInputTextField.autocapitalizationType = .sentences
        }

        alert.addAction(UIAlertAction(title: "Cancel", style: .default, handler: { (action) in
            self.dismiss(animated: true, completion: nil)
        }))

        alert.addAction(UIAlertAction(title: "Add", style: .default, handler: { (action) in

            let textf = alert.textFields![0] as UITextField

            let indexPath = IndexPath(row: self.dataHolder.count, section: 0)

            let itemToInsert = ListItem(title: textf.text!, isChecked: false)

            // self.dataHolder.append(itemToInsert)

            // thought you would want this, it will add your notes in reverse chronological order
            self.dataHolder.insert(itemToInsert, at: 0)
            DefaultsHelper.saveItems(items: self.dataHolder)

            self.myTableView.insertRows(at: [indexPath], with: .automatic)
        }))

        self.present(alert, animated: true, completion: nil)

    }
}

Model classes:

// implementing NSObject and NSCoding to let us save this item in UserDefaults
class ListItem: NSObject, NSCoding{

    var title: String
    var isChecked: Bool

    init(title: String, isChecked: Bool) {
        self.title = title
        self.isChecked = isChecked
    }

    // This code lets us save our custom object in UserDefaults

    required convenience init(coder aDecoder: NSCoder) {
        let title = aDecoder.decodeObject(forKey: "title") as? String ?? ""
        let isChecked = aDecoder.decodeBool(forKey: "isChecked")
        self.init(title: title, isChecked: isChecked)
    }

    func encode(with aCoder: NSCoder) {
        aCoder.encode(title, forKey: "title")
        aCoder.encode(isChecked, forKey: "isChecked")
    }
}

class DefaultsHelper{

    private static let userDefaults = UserDefaults.standard
    private static let dataKey = "dataHolder"

    static var savedItems: [ListItem] {
        guard let savedData = userDefaults.data(forKey: dataKey) else { return [] }

        do{
            let decodedData = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(savedData)
            return decodedData as? [ListItem] ?? []
        }catch{
            print("could not fetch items- you may handle this", error)
        }

        return []
    }

    static func saveItems(items: [ListItem]){
        do{
            let encodedData = try NSKeyedArchiver.archivedData(withRootObject: items, requiringSecureCoding: false)
            userDefaults.set(encodedData, forKey: dataKey)
        }catch{
            print("could not save items- you may handle this", error)
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM