Saving Bool/ Tableview Checkmark - 3Rd Time Lucky

how to I observe cell accessory Type UITableViewCellAccessoryCheckmark is checked or not?

While the answer of @dasblinkenlight is certainly more elegant, it might be too advanced for you.

The essence is to keep track of which cells are checked and which are unchecked. Whenever something changes, reload the table view to updated the checkmarks.

You need to create your own variable to track this. A bitmap is very economical and elegant, but it might be difficult to understand and not scalable to a large number of rows. Alternatively, you could use an array.

NSMutableArray *checkedArray = [NSMutableArray arrayWithObjects:
[NSNumber numberWithBool:YES],
[NSNumber numberWithBool:NO],
[NSNumber numberWithBool:NO],
[NSNumber numberWithBool:NO],
[NSNumber numberWithBool:NO],
[NSNumber numberWithBool:NO],
[NSNumber numberWithBool:NO],
[NSNumber numberWithBool:NO],
[NSNumber numberWithBool:NO],
nil];

To change a row given the index path:

[checkedArray replaceObjectAtIndex:indexPath.row 
withObject:[NSNumber numberWithBool:YES]]; //add

[checkedArray replaceObjectAtIndex:indexPath.row
withObject:[NSNumber numberWithBool:NO]]; //remove

And in cellForRowAtIndexPath make sure you set the accessory explicitly:

if ([[checkedArray objectAtIndex:indexPath.row] boolValue]) {
cell.accessoryType = UITableViewCellAccessoryTypeCheckmark;
}
else {
cell.accessoryType = UITableViewCellAccessoryTypeNone;
}

Restrict cell selection per section in UITableView swift

Fundimentally the best solution (IMHO) for tableviews is to create a view model for your table, manipulate the data as required, then reflect that data in the table. Then, you do everything possible to have the table react to data changes as opposed to trying to use the table view itself to reflect data or state.

EDIT: instead of using reloadData, the code now uses performBatchUpdates for a more elegant presentation.

I created a project that does what you want and you can find it here.

The view data is contained here:

let pilots = "Pilots"
let crew = "Crew"
let passengers = "Passengers"

var sections: [String] = []
var multipleSelectionsAllowed: Set<String> = []

var members: [String: [String]] = [:]
var selectedMembers: Set<String> = []

the first three string constants allow us to index into the data, and initialized:

sections = [pilots, crew, passengers] // initial ordering of sections
multipleSelectionsAllowed = [passengers]

The data is created programmatically, see the attached project or the full code attached below.

You said the sections may change, so sections is a variable and we'll change it later on.

selectedMembers contains a hash of the type (i.e. Pilot, Crew, or Passenger and their name, so it should be unique. This array will reflect the current selections, as data and not indexPaths.

But, we need indexPaths to reflect the isSelected UI changes: fine, we'll use two functions for this:

typealias KeyToValues = (section: String, name: String)

func sectionNameToHash(section: String, name: String) -> String {
let hash = section + "|" + name
return hash
}

func hashToSectionName(hash: String) -> KeyToValues {
let array = hash.components(separatedBy: "|")
assert(array.count == 2)
return (array[0], array[1])
}

Also, something I've found very useful in the past is to put the code that changes the look of a cell in a single place, and call it when a cell is created or changed. You won't get out of sync over time as the UI changes too.

func updateCell(atIndexPath indexPath: IndexPath) {
let cells = tableView.visibleCells
for cell in cells {
guard let path = tableView.indexPath(for: cell) else { continue }
if path == indexPath {
updateCell(cell, atIndexPath: indexPath)
}
}
}

func updateCell(_ cell: UITableViewCell, atIndexPath indexPath: IndexPath) {
let section = sections[indexPath.section]
guard let names = members[section] else { fatalError() }
let name = names[indexPath.row]

let hash = sectionNameToHash(section: section, name: name)
let shouldBeSelected = selectedMembers.contains(hash)

if shouldBeSelected {
cell.accessoryType = .checkmark
print("SELECTED", hash)
} else {
cell.accessoryType = .none
print("DESELECTED", hash)
}
}

You need both because in some cases you only have an indexPath, not the cell.

Note that you use the above methods when creating cells:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)

let section = sections[indexPath.section]
guard let names = members[section] else { fatalError() }
let name = names[indexPath.row]

cell.textLabel?.text = name

updateCell(cell, atIndexPath: indexPath)
return cell
}

When the tableView detects a selection, you will first look at the existing selected data, and first remove that selection from your data, then update any delected cell's UI:

 override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
let section = sections[indexPath.section]
guard let names = members[section] else { fatalError() }

let canMultipleSelect = multipleSelectionsAllowed.contains(section)

if !canMultipleSelect, let paths = tableView.indexPathsForSelectedRows {
for path in paths {
if path.section == indexPath.section {
let name = names[path.row]
let hash = sectionNameToHash(section: section, name: name)
selectedMembers.remove(hash)
updateCell(atIndexPath: path)
tableView.deselectRow(at: path, animated: true)
}
}
}
return indexPath
}

Then, handle the selection method:

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let section = sections[indexPath.section]
guard let names = members[section] else { fatalError() }
let name = names[indexPath.row]
let hash = sectionNameToHash(section: section, name: name)

selectedMembers.insert(hash)
print("SELECTED THE CELL AT", hash)
updateCell(atIndexPath: indexPath)
}

Voila - everything works as you want. But, even better, you can re-arrange the sections as you said you do and get everything properly selected. The example code re-arranges the sections 5 seconds after you select the first row/column

if indexPath.section == 0 && indexPath.row == 0 {
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
self.sections = [self.crew, self.pilots, self.passengers] // changed!
tableView.reloadData()
// all selections from the tableView are now gone
// NOTE: none of the other data changes!
for hash in self.selectedMembers {
let value = self.hashToSectionName(hash: hash)
guard
let sectionNumber = self.sections.firstIndex(of: value.section),
let names = self.members[value.section],
let row = names.firstIndex(of: value.name)
else { fatalError() }

let indexPath = IndexPath(row: row, section: sectionNumber)
self.tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
}
}
}

The reload() erases all selections, so the above code uses the known selected members to inform the tableView of list of selections, even if the cells for each are not visible.

The complete class

import UIKit

private final class MyCell: UITableViewCell {
override var reuseIdentifier: String? { "cell" }
}

final class ViewController: UITableViewController {

let pilots = "Pilots"
let crew = "Crew"
let passengers = "Passengers"

var sections: [String] = []
var multipleSelectionsAllowed: Set<String> = []

var members: [String: [String]] = [:]
var selectedMembers: Set<String> = []

override func viewDidLoad() {
super.viewDidLoad()

tableView.register(MyCell.self, forCellReuseIdentifier: "cell")
tableView.allowsMultipleSelection = true

sections = [pilots, crew, passengers] // initial ordering of sections
multipleSelectionsAllowed = [passengers]

constructData()
}

private func constructData() {
var array: [String] = []
(1..<6).forEach { array.append("Pilot \($0)")}
members[pilots] = array
array.removeAll()

(1..<20).forEach { array.append("Crew \($0)")}
members[crew] = array
array.removeAll()

(1..<250).forEach { array.append("Passenger \($0)")}
members[passengers] = array
}

// MARK: - Helpers -

typealias KeyToValues = (section: String, name: String)

func sectionNameToHash(section: String, name: String) -> String {
let hash = section + "|" + name
return hash
}

func hashToSectionName(hash: String) -> KeyToValues {
let array = hash.components(separatedBy: "|")
assert(array.count == 2)
return (array[0], array[1])
}

}

extension ViewController /*: UITableViewDataSource */ {

override func numberOfSections(in: UITableView) -> Int {
return sections.count
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let type = sections[section]
let count = members[type]?.count ?? 0 // could use guard here too and crash if nil
return count
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)

let section = sections[indexPath.section]
guard let names = members[section] else { fatalError() }
let name = names[indexPath.row]

cell.textLabel?.text = name

updateCell(cell, atIndexPath: indexPath)
return cell
}

func updateCell(atIndexPath indexPath: IndexPath) {
let cells = tableView.visibleCells
for cell in cells {
guard let path = tableView.indexPath(for: cell) else { continue }
if path == indexPath {
updateCell(cell, atIndexPath: indexPath)
}
}
}

func updateCell(_ cell: UITableViewCell, atIndexPath indexPath: IndexPath) {
let section = sections[indexPath.section]
guard let names = members[section] else { fatalError() }
let name = names[indexPath.row]

let hash = sectionNameToHash(section: section, name: name)
let shouldBeSelected = selectedMembers.contains(hash)

if shouldBeSelected {
cell.accessoryType = .checkmark
print("SELECTED", hash)
} else {
cell.accessoryType = .none
print("DESELECTED", hash)
}
}

}

extension ViewController /* : UITableViewDelegate */ {

override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
let section = sections[indexPath.section]
guard let names = members[section] else { fatalError() }

let canMultipleSelect = multipleSelectionsAllowed.contains(section)

if !canMultipleSelect, let paths = tableView.indexPathsForSelectedRows {
for path in paths {
if path.section == indexPath.section {
let name = names[path.row]
let hash = sectionNameToHash(section: section, name: name)
selectedMembers.remove(hash)
updateCell(atIndexPath: path)
tableView.deselectRow(at: path, animated: true)
}
}
}
return indexPath
}

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let section = sections[indexPath.section]
guard let names = members[section] else { fatalError() }
let name = names[indexPath.row]
let hash = sectionNameToHash(section: section, name: name)

selectedMembers.insert(hash)
print("SELECTED THE CELL AT", hash)
updateCell(atIndexPath: indexPath)

if indexPath.section == 0 && indexPath.row == 0 {
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
self.sections = [self.crew, self.pilots, self.passengers]
tableView.reloadData()
// all selections from the tableView are gone

for hash in self.selectedMembers {
let value = self.hashToSectionName(hash: hash)
guard
let sectionNumber = self.sections.firstIndex(of: value.section),
let names = self.members[value.section],
let row = names.firstIndex(of: value.name)
else { fatalError() }

let indexPath = IndexPath(row: row, section: sectionNumber)
self.tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
}
}
}
}

override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
print("DESELECTED THE CELL AT", hash)

let section = sections[indexPath.section]
guard let names = members[section] else { fatalError() }
let name = names[indexPath.row]
let hash = sectionNameToHash(section: section, name: name)
selectedMembers.remove(hash)

updateCell(atIndexPath: indexPath)
}

}

Crash during didSelectRowAtIndexPath with custom index path

I found that your NSIndexPath declaration is one for a "one-node" indexPath. What you require is a TableView indexPath which is declared as:

NSIndexPath(forRow: <#Int#>, inSection: <#Int#>)

So you might want to try the following:

let oldselectedCell:UITableViewCell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: countryKey, inSection: 0))!

Obj-C - Delete tableview row if button in custon tableview cell is tapped

Well I can give a general idea of how this can be achieved in one way.

Declare an Outlet of the button in the cell class

@property (nonatomic, retain) IBOutlet UIButton *deleteButton;

Next in the cellForRowAtIndexPath datasource assign the target to your button to a function within the ViewController. Also assign the tag to this button.

In the action of this function put in the code and logic for deleting the data from your model/datasource and reload the tableView.

Please refer to this link to assign action to your button in the ViewController. Hope this helps.

Code Snippet

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = @"SimpleTableItem";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];

if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}

cell.textLabel.text = [tableData objectAtIndex:indexPath.row];
cell.myButton.tag = indexPath.row
[cell.myButton addTarget:self
action:@selector(myAction)
forControlEvents:UIControlEventTouchUpInside];
return cell;
}

UITableView is starting with an offset in iOS 7

By default table view controllers will pad the content down under the nav bar so you could scroll the content under it and see it, in a blurred state, underneath the navbar/toolbar.

Looks like you're positioning it at 44 (maybe 64)px to move it out from under the nav bar, but it already compensates for this so you get a big gap.

Go to the storyboard/xib in IB and untick the show content under nav bar stuff.

Persisting Checklists on a UITableView using NSUserDefults

Create your tempSource as follows:

NSMutableArray * tempSource = [[NSMutableArray alloc] init];
NSArray *daysOfWeek = [NSArray arrayWithObjects:@"Monday", @"tuestay", @"wednesday", @"thursday", @"friday", @"saturday", @"sunday",nil];
for (int i = 0; i < 7; i++)
{
NSString *dayOfWeek = [daysOfWeek objectAtIndex:i];
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:dayOfWeek, @"day", [NSNumber numberWithBool:NO], @"isSelected",nil];
[tempSource addObject:dict];
}

[self setSourceArray:tempSource];
[tempSource release];

Then use this Array in cellForRow and didSelect as follows:

cellForRow

NSDictionary *dayOfWeekDictionary = [sourceArray objectAtIntex:indexPath.row];

cell.textLabel.text = [dayOfWeekDictionary objectForKey:@"day"];
if ([[dayOfWeekDictionary objectForKey:@"isSelected"] boolValue])
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
else
[cell setAccessoryType:UITableViewCellAccessoryNone];

didSelect

NSDictionary *dayOfWeekDictionary = [sourceArray objectAtIntex:indexPath.row];

if ([[dayOfWeekDictionary objectForKey:@"isSelected"] boolValue])
[dayOfWeekDictionary setObject:[NSNumber numberWithBool:NO] forKey:@"isSelected"];
else
[dayOfWeekDictionary setObject:[NSNumber numberWithBool:YES] forKey:@"isSelected"];

To save this Array use this statement:

[[NSUserDefaults standardUserDefaults] setObject:sourceArray forKey:@"array"];


Related Topics



Leave a reply



Submit