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.