I am trying to achieve to make my TableView only accept one checkbox per cell.
I have used this solution to save User Defaults for the checkmarks which works fine but it allow multiple selections.
I have tried so many solutions on Stack overflow but since this is formatted a little different I was unable to use those solutions.
Things I have tried which did not work:
Tried setting the accessory type to none at the didDeselectRowAt
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
tableView.cellForRow(at: indexPath)?.accessoryType = .none
}
Tried setting the table view to not allow multiple selections on the viewDidNotLoad
override func viewDidLoad()
{
tableViewName.allowsMultipleSelection = false
}
Based the solution I linked, how would I go to only allow a single selection?
In the solution you have linked there is this code in the 'didSelect' function
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
if selectedRows.contains(indexPath.row) {
self.selectedRows = selectedRows.filter{$0 != indexPath.row}
}else{
self.selectedRows.append(indexPath.row)
}
UserDefaults.standard.set(selectedRows, forKey: "selectedRows")
}
If you only want a single checkbox to be selected then you only want a single entity saved in your selectedRows object. Change the above code to:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
self.selectedRows = [indexPath.row]
UserDefaults.standard.set(selectedRows, forKey: "selectedRows")
}
Then every time a checkbox is selected it will clear out all the other selected checkboxes and select only the checkbox you just clicked.
Related
I am doing a screen where there a list o cells with a switch, like an image below;
I have a struct where a save the label of the cell and the switch state value. This struct is loaded at: var source: [StructName] = [] and then source values are attributed to the UITableView cells.
The problem is that when a touch a cell the function: func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) change multiples cells switches states at the same time.
I try to work around the problem by implementing the following function:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let cell = tableView.cellForRow(at: indexPath) as! CustomTableViewCell
for n in 0..<source.count{ // This loop search for the right cell by looking at the cell label text and the struct where the state of the switch is saved
if cell.label.text! == source[n].Label{
// If the label text is equal to the position where the values is saved (is the same order that the cells are loaded in the UITableView) then a change the state of the switch
let indexLabel = IndexPath(row: n, section: 0)
let cellValues = tableView.cellForRow(at: indexLabel) as! CustomTableViewCell
if cellValues.switchButton.isOn {
cellValues.switchButton.setOn(false, animated: true)
source[n].valor = cellValues.switchButton.isOn
} else {
cellValues.switchButton.setOn(true, animated: true)
source[n].valor = cellValues.switchButton.isOn
}
break
}
}
although is saved the right values to the switch state array(source) the visual state of multiples switches also changes even though the cells where never touch.
How could I change my code to select and change only the touched cell?
You should not store / read the state of anything in a cell.
But first things first:
Why do loop through all the values? You should be able to access the row in the data model directly by indexPath.row
You should only modify the model data, not the cell
You then tell the table view to reload the cell, which will then ask the model for the correct data to be displayed.
I would suggest the following:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let row = indexPath.row
source[row].valor.toggle()
tableView.reloadRows(at:[indexPath], with:.automatic)
}
I am trying enable editing of my UITableView by providing an Edit button on the nav bar.
The intention is to allow the user to insert new items and delete existing items.
When clicking the Edit button iOS shows the "Delete" decoration (deleting works fine), but doesnt show "Insert"
I have added navigationItem.rightBarButtonItem = editButtonItem to my UIViewControllers viewDidLoad and have added the following methods to my UITableViewDataSource delegate:
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
// Delete the row from the data source
AppDelegate.persistenceContext.delete(self.fetchedResultsController.object(at: indexPath))
AppDelegate.saveContext()
} else if editingStyle == .insert {
let current = self.fetchedResultsController.object(at: indexPath)
// do rest of insertion logic
}
}
What am I missing something?
Oh and out of interest, if this is possible would the new cell be inserted above or below the one on which the action was initiated?
For the record I can add custom swipe actions to the cells, but the users feedback is they prefer an edit button.
Any light would be appreciated, all the Googling has gotten me is using a button to insert a new row, or adding custom swipe actions.
You need to implement the delegate method:
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle
Return .delete, .insert, or .none for a given index path. Normally only one row (typically first or last) would return .insert and the rest would return .delete. Return .none for any row that can't be inserted or deleted.
It's not uncommon that you also need to override the setEditing(_ editing: Bool, animated: Bool) method to add/remove an extra row just for the purposes of showing the green + (.insert) icon.
An alternative is to show the + icon in the nav bar instead of having a row with the insert icon.
I have a tableview where the user is able to select multiple rows (these rows are distinguished by a checkbox in the row). For some reason, however, I can't implement the functionality to deselect any selected row. Can somebody tell me what I'm missing?
SomeViewController.m
#objc class SomeViewController: UIViewController, NSFetchedResultsControllerDelegate, UITableViewDataSource, UITableViewDelegate {
var deviceArray:[Device] = []
// [perform a fetch]
// [insert fetch results into deviceArray to be displayed in the tableview]
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "customcell", for:
indexPath)
// Set up the cell
let device = self.deviceArray[indexPath.row]
cell.textLabel?.text = device.name
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.tableView(tableView, cellForRowAt: indexPath).accessoryType = UITableViewCellAccessoryType.checkmark
NSLog("Selected Row")
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
self.tableView(tableView, cellForRowAt: indexPath).accessoryType = UITableViewCellAccessoryType.none
NSLog("Deselected Row")
}
}
More info:
Looking at the debug logs inserted, I can share the following observations:
When selecting an unselected row, the console prints "Selected Row"
If I click the same row from observation #1, the console prints "Selected Row" only
If I click on any other row, the console prints "Deselected Row" and then "Selected Row"
If I click on the same row as observation #3, the console prints "Selected Row" only.
So, it looks like everytime I click on a different row, tableView: didDeselectRowAt: gets called; however, checkmarks in the clicked row do not go away.
More Info 2:
So I'm new to storyboards and didn't set the "allowsMultipleSelection" property. Going into the Attributes Inspector, this is what my settings look like:
Now, when pressing the same row in the tableView, my console confirms that the app is alternating between tableView:didSelectRowAt: and tableView:didDeselectRowAt:, however, the checkmark doesn't disappear; once the user selects a row, the checkmark remains selected even when tableView:didDeselectRowAt: is called. What else am I missing?
First off make sure your datasource AND delegate outlets are set if you are setting them from storyboard.
Another thing is you need to set allowsMultipleSelection property to true to get the didSelect, didDeselect methods to get called in the behavior you want. Otherwise it will always call didSelect for the cell you tapped on and didDeselect for the most previously selected cell.
The other thing to note is that you are referencing self.tableView when setting your cell.accessoryType property. This may be different instance of the tableView being passed into the delegate method. I recommend a guard let statment to ensure the code setting the accessory type only applies if the tableView being passed into the function. Here is code I used to get it to work.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//Notice I use tableView being passed into func instead of self.tableView
guard let cell = tableView.cellForRow(at: indexPath) else {
return
}
cell.accessoryType = .checkmark
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
guard let cell = tableView.cellForRow(at: indexPath) else {
return
}
cell.accessoryType = .none
}
If you want user to be able to select multiple cells, you need to set tableView.allowsMultipleSelection = true in the viewDidLoad method or set multiple selection in the attributes inspector.
When I click on Edit button in my screen i change my TableView to edit mode and set the edit style as check box by doing this
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
return UITableViewCellEditingStyle.init(rawValue: 3)!
}
now I need programatically pre check some of the entries.
How can i Make some of the cells as checked?
I know in
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
I have to set the edit style which marks it as checked.
I finally figured it out.
Setting the Checkbox in an table view is equivalent to selecting the cell.
so all you need to do is mark the sell as selected, nothing to do with the Edit style
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath) as! MyCell
let item = self.items[indexPath.row]
cell.setup(item: item)
cell.setSelected(true, animated: true) // Provided your cell is already in check box edit mode, then this makes it CHECK ON
return cell
}
By the way. I saw an explanation in similar question here (on SO) that you should not use this:
return UITableViewCellEditingStyle.init(rawValue: 3)!
Because it's not public API and if Apple changes this value, application will stop working properly. The same behaviour can be reached simply by enabling Multiple selection during editing:
You can check it easily by setting breakpoint on your overridden method
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
return UITableViewCellEditingStyle.init(rawValue: 3)!
}
And enable editing during running the app with Multiple selection during editing and without it. You will see that if Multiple selection during editing is enabled, this method will not be called at all.
I am trying to learn Swift, but there is a problem in my project that drives me nuts.
I have a working list of data in a ViewController fed by parse.com. I managed to implement a swipe-feature that reveals buttons for both deleting and editing. That is working fine. Now I want the user to be able to reorder the cells. So I successfully implemented a button to put the table into editing-mode. My 2 problems with that are:
When I enter edit-mode I just want to be able to reorder the cells since editing and deleting is done via swipe (via "tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath)". How can I achive that the user doesn't see the 2 buttons for deleting and editing when in editing-mode and touching the delete-circle that is provided automatically?
Is it possible to remove the delete-circle altogether? Using "UITableViewCellEditingStyle.None" also disables the swipe-functionality.
Thanks in advance!
To avoid the round red delete button that appears when you put set UITableView isEditing to true at the left and does nothing when you click it, the minimum that worked for me was this (Swift 4, iOS 11)
// Avoid the round red delete button on the left of the cell:
func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
return false
}
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
return .none
}
I also have these functions, which probably interact:
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return savedTempTable.isEditing
}
// Including this function in the delegate enable left-swipe deleting
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath)
{
if editingStyle == .delete {
savedConversions.remove(at: indexPath.row)
}
}
// Including this function enables reordering
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath,to destinationIndexPath: IndexPath)
{
let elem = savedConversions.remove(at: sourceIndexPath.row)
savedConversions.insert(elem, at: destinationIndexPath.row)
}
override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
return .none
}
override func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
return false
}
Although people can delete a row through swipe the delete-button in editing mode should not be removed. People may not know about the swipe gesture and by removing the delete button (which they already expect in editing mode) the app becomes more difficult to use.
If you really want to remove the delete button then you have to implement the delegate method tableView(_:editingStyleForRowAtIndexPath:). There you can return .None while the screen is in editing mode and .Delete while the screen is not.
To enable reordering you have to implement the data source methods tableView(_:canMoveRowAtIndexPath:) and tableView(_:moveRowAtIndexPath:toIndexPath:).
You follow this way to remove the delete Icon while editing:
-(UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath{
return UITableViewCellAccessoryNone;
}