Checkbox Issue in tableview cell - ios

I have a multiple checkbox in tableview cell and total number of cell 4,i want to select one checkbox at one time and all check box is unselect if i select any one of check box and also one button in table view cell button are also disable of other cell so how i implement this logic.here i implement image check and uncheck logic
func setupData(plansModel: [Plans], mainRowIndex: Int, selctedPlan: Int, isAllUnselected: Bool){
self.plansModel = plansModel
indexMain = mainRowIndex
selectedPlanId = selctedPlan
self.isAllUnselected = isAllUnselected
self.changePlanBtn.isHidden = isAllUnselected
priceTableView.reloadData()
}
i take this variable for data and manage checkbox

I think you want this
First
second
You can do this Way!
Firts make a variable
var selectedIndex: IndexPath = IndexPath(row: 0, section: 0)
Second in tableview didselecte method add this
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let previousSelectedIndex = selectedIndex
selectedIndex = indexPath
checkBoxTableView.reloadRows(at: [previousSelectedIndex, selectedIndex], with: .none)
}
third and last add this code in tableview cellForRow Method
if selectedIndex.row == indexPath.row {
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
I think your answer is solved.
This Was edited For realod only two row rather than whole tableview (this edit rafrence by paulw11).
Thank you Paulw11 For helping!

Related

Can’t deselect previously selected cell when a new one is selected in a tableview where each cell contains tableview

I have a tableview, where each cell is a custom tableview cell. That custom tableview cell contains a tableview and a label.
Lets say, the outer tableview is called MainTableView. Each cell of MainTableView consists another tableview. The problem is , when I select
inner tableview cell one by one the previously selected cell is not get deselected.In first image I have selected cell contains text “Two”. Then In the second image I have selected cell contains text “Five” but perviously selected cell “Two” still in selection mode. I want to deselect the previous cell when select a new one.
I have tried
tableView.deselectRow(at: IndexPath, animated: Bool)
this method inside didSelectRowAt in custom tableviewcell class but it didn’t serve the purpose because the previous indexPath is from seperate tableview. So, how can I deselect the previous one?
to get the correct inner tableView,
Firstly , you should record the outside tableView's cell indexPath, which cell the inner tableView is in.
So your should record two indexPathes
var selectionRowInfo: (lastOutsideIP: IndexPath?, lastInnerIP: IndexPath?)
Secondly, you get the correct tableView, via the outsideTableView.
if the inner table is visible, you shall handle it immediately. through outTableView.indexPathsForVisibleRows
the else condition, you need not handle it. The tableView reuse mechanism will refresh its state.
// pseudo code.
if let lastOut = lastOutsideIP, let visibleRows = outTableView.indexPathsForVisibleRows, visibleRows.contains(lastOut){
let cell = tableView.cellForRow(at: lastOut) as! YourCell
// get the correct inner tableView via the cell
}
Because the inner tableViews are not related to each other. Select the cell of table one, will not affect the selection of cell of table two.
So your should build the connection manually.
Use a property to store the state var lastIndexPath: IndexPath?,
then every time select a indexPath,
// pseudo code.
if let last = lastIndexPath{
tableView.deselectRow(at: last, animated: true)
}
Please notice that, you should find the correct inner tableView, which has the lastIndexPath
The previous answer is along the right lines but has a flaw - it cannot distinguish between tableViews, which is the most important part. Also if the tableViews have different numbers of rows, it risks trying to access a row that doesn't exist and causing a crash.
To track the selected row in two tableViews (tv1 & tv2) you'll need to hold the selected row in each:
var tv1, tv2: UITableView!
var lastRowForTV1, lastRowForTV2: IndexPath?
and then respond to selections by identifying the tableView being used and adjusting the other (this assumes the two tableViews use the same datasource/delegate)
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableView === tv1 {
lastRowForTV1 = indexPath
if let last = lastRowForTV2 {
tv2.deselectRow(at: last, animated: true)
lastRowForTV2 = nil
}
} else if tableView === tv2 {
lastRowForTV2 = indexPath
if let last = lastRowForTV1 {
tv1.deselectRow(at: last, animated: true)
lastRowForTV1 = nil
}
}
}
I have solved the problem by using the idea of first answer given by dengApro . The idea was to find the correct inner table which contains the previously selected cell.
I have two files one is ViewController.swift that contains the outer tableview MainTableView. Another one is CustomTableViewCell.swift with CustomTableViewCell.xib that contains the custom cell with tableview.
fileprivate var lastSelectedInnerTableView : Int = -1
fileprivate var lastSelectedRow: Int = -1
fileprivate var tableViewList :[UITableView] = []
I have added these 3 variables in CustomTableViewCell.swift file outside the class CustomTableViewCell. lastSelectedSection , lastSelectedRow these 2 variable are used to keep
track of the last selected inner tableview (lastSelectedInnerTableView) and the last selected cell of that inner tableView (lastSelectedRow). tableViewList variable is used to keep the
Inner tableView. Inside awakeFromNib() I have append the created inner tableviews.
override func awakeFromNib() {
super.awakeFromNib()
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
tableViewList.append(self.tableView) // append the created inner tableviews
}
Then inside didSelectRowAt I have deselect the previous one:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if lastSelectedInnerTableView != -1 && lastSelectedRow != -1{
self.oldIndexPath.section = 0
self.oldIndexPath.row = lastSelectedRow
tableViewList[lastSelectedInnerTableView].deselectRow(at: self.oldIndexPath, animated: true)
}
lastSelectedInnerTableView = self.innerTableId
lastSelectedRow = indexPath.row
}
In ViewController.swift I have set innerTableId inside cellForRowAt
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let customCell: CustomTableViewCell = self.tableView.dequeueReusableCell(withIdentifier: "customCell") as! CustomTableViewCell
customCell.innerTableId = indexPath.row
customCell.customCellActionDelegate = self
return customCell
}

How to save the selected choice/answer of a questionnaire as a string in swift?

I am trying to create a questionnaire in Xcode using swift (tableview) where the user of the app can choose their preferred choice (for example one question can be, what days are you available? and the user has a list of options to choose from and can select multiple)
To do this, I have created a variable called potans (short for potential answers) and another variable called selectedans which holds the information regarding the selected answers.
When the user clicks one of the answers, a checkmark appears next to the selected choice and also the row number of the selected answer is printed. When the user again clicks on the already selected option, they automatically deselect it and the checkmark is removed and the row number of the deselected row is again printed.
I would like to be able to store the selected answer/s when the user presses the 'next button'. However, since the user can change their mind (select and then deselect) and can select multiple answers, I do not know how to convert the information of row number to the string name.
So when the user presses (refer to the code I have written) ans1, a checkmark appears and row:0 is printed (output). If the user does not do anything else and presses the 'next button' everything is alright. However, if the user deselects ans1 (we get another row:0 printed) and then selected ans2 and ans3 (where we then get row:1 and row:2 printed), so what line of code would I need to add to be able to conclude that the final answers of the user have been ans2 and ans3?
I had thought of somehow making two of the same row numbers (either consecutively or not) cancel out but I do not know how to do that.
var potans = ["ans1", "ans2", "ans3", "ans4", "ans5"]
var selectedans:[String] = []
//some irrelevant code here
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return potans.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = potans[indexPath.row]
let selectedView = UIView()
selectedView.backgroundColor = UIColor(red:0.65, green:0.85, blue:0.62, alpha:1.0)
cell.selectedBackgroundView = selectedView
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableView.cellForRow(at: indexPath as IndexPath)?.accessoryType == .checkmark
{
tableView.cellForRow(at: indexPath as IndexPath)?.accessoryType = .none
} else {
tableView.cellForRow(at: indexPath as IndexPath)?.accessoryType = .checkmark
}
selectedans.append(potans[indexPath.row])
print("row: \(indexPath.row)")
}
//Created a 'next' button going to the next question
#objc func buttonAction(sender: UIButton!) {
self.performSegue(withIdentifier: "segue", sender: self)
}
Right now, the output is just the row numbers that are selected AND deselected. In reality, I would like an output that shows the final row number/s, disregarding the selected and then deselected answers.
You can use tableView(_:didDeselectRowAt:) method of UITableViewDelegate and delete related data in your selectedans array in this method.

UITableView sections with single and multiple selection

How do I make an UITableView which allows sections to either have single or multiple selection?
I have a single UITableView with multiple sections. I want some sections to allow multiple selections and some to only allow a single selection. This is what I currently have:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
// Get the current question
if var question = self.questions[indexPath.section] as? MultipleChoiceQuestion {
// If question allows multiple selection set a checkmark and update question with selected answer
if question.hasMultiValue {
cell?.accessoryType = .checkmark
question.givenAnswer = question.answers[indexPath.row]
} else {
// Question is single selection only. This entire part goes wrong.
if let givenAnswerIsEmpty = question.givenAnswer {
cell?.accessoryType = .none
} else {
self.questions[indexPath.section].givenAnswer = question.answers[indexPath.row]
cell?.accessoryType = .checkmark
}
}
}
}
My objects all have the property hasMultiValue which indicates if a section should allow multiple selections or not. They also have the property givenAnswer which could be seen as a "isSelected" flag. The code above only works for multi selection.
I've been searching around for a solution. There are a few questions like this one but the solution involves using the delegate method didDeselectRowAt. This method won't get called unless I physically deselect the current cell which is not what I want.
What I do want is for example:
If you select row1 and change your mind to row2, you should be able to select row2 which automatically deselects row1.
You didn't try willSelectRowAt?
You can try this code, this is tested on a table view controller
var selectedItemInSection1: Int?
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
// Just return the same index path if the section was not the second one
guard indexPath.section == 1 else { return indexPath }
// Get the previously selected item, and deselect it
if let prev = selectedItemInSection1 {
tableView.deselectRow(at: IndexPath.init(row: prev, section: 1), animated: true)
}
// Save the newly selected item index, to be deselected when other is selected in the same section
selectedItemInSection1 = indexPath.row
// Select the item
return indexPath
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
guard indexPath.section == 1 else { return }
// If it is in the second section, indicate that no item is selected now
selectedItemInSection1 = nil
}

filterTableViewController.reloadRows reloading rows only on first call

I have a table with 3 rows each with check button.What I am doing is when I select all the three buttons I want to click my cancel button which is on view not table on same controller to reload all 3 rows the call goes to custom cell class where uncheck is set to true and rows are reloaded.For the first attempt it works fine I can see correct index to be reloaded.On the second time again when I select all 3 check buttons and click cancel again I can see correct index to be reloaded but the call is not going to custom cell class again the check box still remains checked.Any idea why?
I am always getting correct index in my array.
Cancel button code-:
#IBAction func cancelDataItemSelected(_ sender: UIButton) {
for index in selectedButtonIndex{
let indexPath = IndexPath(item: index, section: 0)
print(selectedButtonIndex)
filterTableViewController.reloadRows(at: [indexPath], with: UITableViewRowAnimation.none)
}
selectedButtonIndex .removeAll()
print(selectedButtonIndex)
}
Table code-:
extension filterControllerViewController:UITableViewDataSource,UITableViewDelegate
{
// NUMBER OF ROWS IN SECTION
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return ControllerData.count
}
// CELL FOR ROW IN INDEX PATH
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let Cell = tableView.dequeueReusableCell(withIdentifier: "filterCell", for: indexPath) as! ControllerCellTableViewCell
Cell.filterTableMenu.text = ControllerData[indexPath.item]
Cell.radioButtonTapAction = {
(cell,checked) in
if let radioButtonTappedIndex = tableView.indexPath(for: cell)?.row{
if checked == true {
self.selectedButtonIndex.append(radioButtonTappedIndex)
}
else{
while self.selectedButtonIndex.contains(radioButtonTappedIndex) {
if let itemToRemoveIndex = self.selectedButtonIndex.index(of: radioButtonTappedIndex) {
self.selectedButtonIndex.remove(at: itemToRemoveIndex)
}
}
}
}
}
return filterCell
}
Custom Class-:
var radioButtonTapAction : ((UITableViewCell,Bool)->Void)?
//MARK-:awakeFromNib()
override func awakeFromNib() {
super.awakeFromNib()
filterTableSelectionStyle()
self.isChecked = false
}
// CHECKED RADIO BUTTON IMAGE
let checkedImage = (UIImage(named: "CheckButton")?.withRenderingMode(UIImageRenderingMode.alwaysOriginal))! as UIImage
// UNCHECKED RADIO BUTTON IMAGE
let uncheckedImage = (UIImage(named: "CheckButton__Deselect")?.withRenderingMode(UIImageRenderingMode.alwaysOriginal))! as UIImage
// Bool STORED property
var isChecked: Bool = false {
didSet{
// IF TRUE SET TO CHECKED IMAGE ELSE UNCHECKED IMAGE
if isChecked == true {
TableRadioButton.setImage(checkedImage, for: UIControlState.normal)
} else {
TableRadioButton.setImage(uncheckedImage, for: UIControlState.normal)
}
}
}
// FILTER CONTROLLER RADIO BUTTON ACTION
#IBAction func RadioButtonTapped(_ sender: Any) {
isChecked = !isChecked
radioButtonTapAction?(self,isChecked)
}
Fundamental misunderstanding of how "reusable" table cells work.
Let's say your table view is tall enough that only 8 cells are ever visible. It seems obvious that 8 cells will need to be created, and they will be reused when you scroll.
What may not be obvious is that the cells also are reused when they are reloaded. In other words, every time .reloadData is called - even if you are only reloading one cell that is currently visible - that cell is reused. It is not re-created.
So, the key takeaway point is: Any initialization tasks happen only when the cell is first created. After that, the cells are reused, and if you want "state" conditions - such as a checked or unchecked button - it is up to you to "reset" the cell to its original state.
As written, your cellForRowAt function only sets the .filterTableMenu.text ... it ignores the .isChecked state.
You can mostly fix things just by setting the cell's .isChecked value, but you're also tracking the on/off states in a much more complicated manner than need be. Instead of using an Array to append / remove row indexes, use an Array of Booleans, and just use array[row] to get / set the values.
Then your cellForRowAt function will look about like this:
// CELL FOR ROW IN INDEX PATH
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let filterCell = tableView.dequeueReusableCell(withIdentifier: "filterCell", for: indexPath) as! ControllerCellTableViewCell
// set the label in filterCell
filterCell.filterTableMenu.text = ControllerData[indexPath.item]
// set current state of checkbox, using Bool value from out "Tracking Array"
filterCell.isChecked = self.selectedButtonIndex[indexPath.row]
// set a "Callback Closure" in filterCell
filterCell.radioButtonTapAction = {
(checked) in
// set the slot in our "Tracking Array" to the new state of the checkbox button in filterCell
self.selectedButtonIndex[indexPath.row] = checked
}
return filterCell
}
You can see a working example here: https://github.com/DonMag/CheckBoxCells
Remember that the cells are reused and that reloadRows just tells the rows to redraw. When a checkbox in a cell is checked by the user, the new checked state should be saved in the underlying data source, and the state marked in the cell in cellForRowAtIndexPath. Otherwise the cell checkbox shows the state for the last time it was set by the user for all indices and not the state for the underlying data source.

Add/Remove UITableView Selection to Array, Checkmarks Disappear upon scroll - Swift

Issue 1: Check Marks Keep Disappearing when scrolling.
Issue 2: Need help adding/removing from array with unique ID to prevent duplicates.
I am trying to insert/remove a cellTextLabel from an empty array. I can't seem to find a good solution. Here's what I've tried and why it failed.
Bad Option 1
// Error = Out of range (I understand why)
myArray.insert(cell.textLabel.text, atIndex: indexPath)
Bad Option 2
// I won't have a way to reference the array item afterwards when I need to remove it. Also, this option allows for the same string to be entered into the array multiple times, which is not good for me.
myArray.insert(cell.textLabel.text, atIndex: 0)
Below is the code so far, any help is greatly appreciated. Thank you.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let row = indexPath.row
let cell : UITableViewCell = tableView.dequeueReusableCellWithIdentifier("items", forIndexPath: indexPath) as UITableViewCell
var myRowKey = myArray[row]
cell.textLabel.text = myRowKey
cell.accessoryType = UITableViewCellAccessoryType.None
cell.selectionStyle = UITableViewCellSelectionStyle.None
return cell
}
func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
let selectedCell = tableView.cellForRowAtIndexPath(indexPath) as UITableViewCell!
if selectedCell.accessoryType == UITableViewCellAccessoryType.None {
selectedCell.accessoryType = UITableViewCellAccessoryType.Checkmark
}
var selectedItem = selectedCell.textLabel.text!
println(selectedItem)
}
func tableView(tableView: UITableView!, didDeselectRowAtIndexPath indexPath: NSIndexPath!) {
let deSelectedCell = tableView.cellForRowAtIndexPath(indexPath) as UITableViewCell!
if deSelectedCell.accessoryType == UITableViewCellAccessoryType.Checkmark {
deSelectedCell.accessoryType = UITableViewCellAccessoryType.None
}
var deSelectedItem = deSelectedCell.textLabel.text!
println(deSelectedItem)
}
Issue 1: Your checkmarks keep disappearing when you're scrolling because of the following line in the didDeselectRowAtIndexPath method:
let deSelectedCell = tableView.cellForRowAtIndexPath(indexPath) as UITableViewCell!
The call to cellForRowAtIndexPath will create a NEW cell. It will NOT modify the currently visible cell on the screen in your UITableView. This is because cells are reused as items scroll on and off the screen, with new data loaded into them.
To retain the selection status of your cells, you will need to upgrade your data model a bit. Right now your data comes from the myArray which is a String array. You could try something as follows instead:
struct Item {
var name: String // The string value you are putting in your cell
var isSelected: Bool // The selected status of the cell
}
Then you would define your data something like this:
var myArray = [
Item(name: "Cell 1 value", isSelected: false),
Item(name: "Cell 2 value", isSelected: false),
...
]
And your tableView:didSelectRowAtIndexPath method would look more like this:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
// Toggle the selected state of this cell (e.g. if it was selected, it will be deselected)
items[indexPath.row].isSelected = !items[indexPath.row].isSelected
// Tell the table to reload the cells that have changed
tableView.beginUpdates()
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.None)
tableView.endUpdates()
// Reloading that cell calls tableView:numberOfRowsInSection and refreshes that row within the tableView using the altered data model (myArray array)
}
Next time you tap that row, the tableView:didSelectRowAtIndexPath method will fire again and toggle the selected state of that cell. Tell that cell to reload will refresh the cell that is actually visible on the screen.
Issue 2: Without knowing too much about the type of data you want to keep unique and how you are adding/removing in ways that could add duplicates, you might want to take a look at this answer for removing duplicate elements from your array. One way is to use a set, which will not preserve order but will ensure elements only occur once.

Resources