Hi I am having a problem in my app
I am using UITableView with the custom cell. In each cell, I have a
checkbox, when I check it, it is getting the cell’s element into a
set. Everything is working correctly except a small problem.
The Problem:
2
As in the image when I click on the first row, the 10th row is seeming
like checked. The image changed to checked but in practice, it did
not occur in my set.
Here is the related part of the code
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: TblCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as! TblCell
cell.labelInCell.text = tableData[indexPath.row]//data.myFunc().myset[indexPath.row]
cell.checkBoxInCell.tag = indexPath.row
cell.checkBoxInCell.addTarget(self, action: Selector("yourCheckBoxClicked:"), forControlEvents: .TouchUpInside)
// cell.images.image = UIImage(named: tableData[indexPath.row])
// if images name is same as in tableData put it in front of label
return cell
}
func yourCheckBoxClicked(cbx:UIButton){
let picked = self.tableData[cbx.tag]
if choosenSet.contains(picked) {
choosenSet.remove(picked) // uncheck
} else {
choosenSet.insert(picked) // check
}
print(choosenSet)
}
Number of rows in section ->
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableData.count
}
Checkboxes class ->
class checkBox: UIButton {
//images
let checkedImage = UIImage(named: "button_checkboxFilled")
let uncheckedImage = UIImage(named: "button_checkboxEmpty")
// Bool Property
var isChecked : Bool = false {
didSet{
if isChecked == true{
self.setImage(checkedImage, forState: .Normal)
}else {
self.setImage(uncheckedImage, forState: .Normal)
}
}
}
override func awakeFromNib() {
self.addTarget(self, action: "buttonClicked:" , forControlEvents: UIControlEvents.TouchUpInside)
self.isChecked = false
}
func buttonClicked (sender:UIButton) {
if (sender == self) {
if isChecked == true {
isChecked = false
}else {
isChecked = true
}
}
}
}
This is because of cells reusage. You should just add this line to your func tableView(tableView:cellForRowAtIndexPath:) method:
cell.checkBoxInCell.isChecked = choosenSet.contains(tableData[indexPath.row])
Your cells are reused, quick solution which will work fine as your table view doesn't have a lot of cells:
let cell: TblCell = self.tableView.dequeueReusableCellWithIdentifier("cell" + String(indexPath.row)) as! TblCell
If your tableView has many rows than you should have "cell" identifier and update the selection style on each cellForRowAtIndexPath call
Related
This is not a massive issue with me, I am just struggling to understand what is happening. Other rather how to make it work the way I want it to work.
Consider the following code of any standard UITableViewController:
var tapGestureRecognizer = UITapGestureRecognizer()
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let customCell = tableView.dequeueReusableCell(withIdentifier: customCellID) as? CustomTableViewCell else { return UITableViewCell() }
if indexPath.row == 0 {
print("Inside cellForRowAt: \(indexPath.row)")
customCell.backgroundColor = .red
tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tagIndexPathRowMethod))
tapGestureRecognizer.cancelsTouchesInView = false
tapGestureRecognizer.view?.tag = indexPath.row
customCell.isUserInteractionEnabled = true
customCell.addGestureRecognizer(tapGestureRecognizer)
return customCell
} else {
print("Inside cellForRowAt: \(indexPath.row)")
customCell.backgroundColor = .blue
tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tagIndexPathRowMethod))
tapGestureRecognizer.cancelsTouchesInView = false
tapGestureRecognizer.view?.tag = indexPath.row
customCell.isUserInteractionEnabled = true
customCell.addGestureRecognizer(tapGestureRecognizer)
return customCell
}
}
#objc private func tagIndexPathRowMethod(sender: UITapGestureRecognizer) {
print("Cell number tag: \(String(describing: sender.view?.tag))")
}
I've already tried splitting the properties, methods and cells into separate codes such as
var firstTapGestureRecognizer = UITapGestureRecognizer()
var secondTapGestureRecognizer = UITapGestureRecognizer()
etc, but the tag still prints only a 0 from both cells.
Could someone explain to me how to make the print statement in tagIndexPathRowMethod returns 0 as tag no matter if I tap in cell 0 or cell 1, but the print statements inside cellForRowAt prints the correct indexPath.row integers, 0 and 1? I know I could use didSelectRowAt, but I've just become stubborn I guess.
(I'm well aware of all times I'm breaking with the DRY principle, but it just serves as a pedagogical example.)
Updated answer
This happening because the you setting tag before adding gestures to the cell. In this case, tapGestureRecognizer.view is null at that time. Just do one thing set tag after adding gestures to the cell.
customCell.addGestureRecognizer(tapGestureRecognizer)
tapGestureRecognizer.view?.tag = indexPath.row
You need to set the value of view tag inside the UITapGestureRecognizer class. Just add the line below after initializing 'customCell'.
customCell.tag = indexPath.row
Code:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let customCell = tableView.dequeueReusableCell(withIdentifier: customCellID) as? CustomTableViewCell else { return UITableViewCell() }
customCell.tag = indexPath.row
if indexPath.row == 0 {
print("Inside cellForRowAt: \(indexPath.row)")
customCell.backgroundColor = .red
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tagIndexPathRowMethod))
tapGestureRecognizer.cancelsTouchesInView = false
tapGestureRecognizer.view?.tag = indexPath.row
customCell.isUserInteractionEnabled = true
customCell.addGestureRecognizer(tapGestureRecognizer)
return customCell
} else {
print("Inside cellForRowAt: \(indexPath.row)")
customCell.backgroundColor = .blue
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tagIndexPathRowMethod))
tapGestureRecognizer.cancelsTouchesInView = false
tapGestureRecognizer.view?.tag = indexPath.row
customCell.isUserInteractionEnabled = true
customCell.addGestureRecognizer(tapGestureRecognizer)
return customCell
}
}
#objc private func tagIndexPathRowMethod(sender: UITapGestureRecognizer) {
print("Cell number tag: \(String(describing: sender.view?.tag))")
}
You should not use cellForRow to add tapGesture to the cell ... as cell get reuse so same gesture will apply to multiple cells. So instead of adding it in cell for row .. add them in Custom cell init() method in your case in CustomTableViewCell class so it adds only once ... you can set Tag to that gesture in cellForRow method that will not cause any issue ...
I have multiple sections in my tableview with multiple custom cells(cell with radio button, cell with check box and cell with textfield). Problem is under section with radio button, only one radio button should be selectable under radio button section. After selecting, I have tried scrolling, multiple radio buttons are selected. Help much appreciated.
class RedioButtonCell: UITableViewCell {
var radioButtonDelegate: RedioCellDelegate?
var cellindexPath : IndexPath?
#IBOutlet weak var btnRediouttion: UIButton?
#IBOutlet weak var lblTitle: UILabel?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
}
Cell for row method:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if ((qaArray[indexPath.section]["Que"] as! [String:String])["type"]!) == CONTROL
{
let cell = tableView.dequeueReusableCell(withIdentifier: "RedioButtonCell", for: indexPath) as! RedioButtonCell
cell.btnRediouttion?.tag = Int("\(indexPath.section)" + "\(indexPath.row)")!
cell.lblTitle!.text = String(describing: ansDetailArray[indexPath.row]["survey_answer"]!)
let deselectedImage = UIImage(named: "Radio_Unselected")?.withRenderingMode(.alwaysTemplate)
let selectedImage = UIImage(named: "radio_Selected")?.withRenderingMode(.alwaysTemplate)
btnRediouttion?.setImage(deselectedImage, for: .normal)
btnRediouttion?.setImage(selectedImage, for: .selected)
btnRediouttion?.addTarget(self, action: #selector(self.radioButtonTapped), for: .touchUpInside)
cell.cellindexPath = indexPath;
return cell
}
}
func radioButtonTapped(_ radioButton: UIButton) {
print("radio button tapped")
let isSelected = !(radioButton?.isSelected)!
radioButton?.isSelected = isSelected
if isSelected {
}
}
The tableView cells are reused ( happens when scrolling ) , so you need to keep track of the selected one by saving it's IndexPath and assign it inside cellForRowAt
//
declare this in your VC
var currentIndex:IndexPath?
//
class RadioButton:UIButton {
var indexPath:IndexPath
}
//
func radioButtonTapped(_ radioButton: RadioButton) {
self.currentIndex = radioButton.indexPath
}
//
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath == currentIndex {
// this should be selected
}
else {
// Deselect this
}
cell.radioButton.indexPath = indexPath
}
This I what I trying to achieve:
There is no problem in populating all the datas, but the problem is how to let user to choose only one row for radio section and allow more than one row for checkbox section, I am stuck in this part. Here is the code for now:
#objc func checkboxSelected(_ sender: UIButton) {
sender.isSelected = !sender.isSelected
if(sender.isSelected == true)
{
sender.setImage(UIImage(named: "red_checkbox"), for: UIControlState.normal)
}
else
{
sender.setImage(UIImage(named: "checkbox2"), for: UIControlState.normal)
}
}
#objc func radioBtnSelected(_ sender: UIButton) {
sender.isSelected = !sender.isSelected
if(sender.isSelected == true)
{
sender.setImage(UIImage(named: "radio_red"), for: UIControlState.normal)
}
else
{
sender.setImage(UIImage(named: "radio_white"), for: UIControlState.normal)
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "DishTableViewCell", for: indexPath) as! DishTableViewCell
cell.titleLbl.text = self.menu[indexPath.section].menuOptions[indexPath.row].name
if self.menu[indexPath.section]. menuOptions[0].title == "checkbox" {
cell.checkboxBtn.isHidden = false
cell.radioBtn.isHidden = true
}else if
self.menu[indexPath.section]. menuOptions[0].title == "radio" {
cell.checkboxBtn.isHidden = true
cell.radioBtn.isHidden = false
}
cell.checkboxBtn.addTarget(self, action: #selector(DishViewController.checkboxSelected(_:)), for: UIControlEvents.touchUpInside)
cell.radioBtn.tag = self.menu[indexPath.section]. menuOptions[indexPath.row].id
cell.radioBtn.addTarget(self, action: #selector(DishViewController.radioBtnSelected(_:)), for: UIControlEvents.touchUpInside)
return cell
}
Or is there any other way to do other than using tableview? Please assist. Thank you
I would suggest you to use UIImageView instead of button and use tableView didSelectRowAt method.
I have edited your code and made some changes below:
1.Declare two variables for keeping track of indexpath
var radioButtonIndexPath = [Int:IndexPath]() //for radiobutton
var checkboxIndexPath = [indexPath]() //for checkbox
2.cellForRowAt method has modified with an UIImageView instead of UIButton
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "DishTableViewCell", for: indexPath) as! DishTableViewCell
cell.titleLbl.text = self.menu[indexPath.section].menuOptions[indexPath.row].name
if self.menu[indexPath.section]. menuOptions[0].title == "checkbox" {
cell.checkbox.isHidden = false
cell.radio.isHidden = true
if checkboxIndexPath.contains(IndexPath) {
checkbox.image = UIImage(named:"red_checkbox")
}else{
checkbox.image = UIImage(named:"checkbox2")
}
}else if
self.menu[indexPath.section]. menuOptions[0].title == "radio" {
cell.checkbox.isHidden = true
cell.radio.isHidden = false
if radioButtonIndexPath.keys.contains(indexPath.section) {
if radioButtonIndexPath[indexPath.section] == indexPath {
radio.image = UIImage(named:"radio_red")
}else{
radio.image = UIImage(named:"radio_white")
}
}else{
radio.image = UIImage(named:"radio_white")
}
}
return cell
}
3.didSelectRowAt method:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if self.menu[indexPath.section].menuOptions[0].title == "checkbox" {
if checkboxIndexPath.contains(IndexPath) {
checkboxIndexPath.remove(at: checkboxIndexPath.index(of: IndexPath))
}else{
checkboxIndexPath.append(IndexPath)
}
}else if self.menu[indexPath.section].menuOptions[0].title == "radio" {
radioButtonIndexPath[indexPath.section] = indexPath
}
yourtableView.reloadData() // reload your tableview here
}
try this code and let me know if there any issue.
In tableview have different section.
Want to add the radio button for all the section.
Each section have individual select and deselect in tableview.
In first section choice1,[show in fig]
Selected cheese means cheese want to select, next if user click bacon means cheese automatically deselect.
[Here using radio button SSRadioButton class for click action. Create a radio button in tableview cell. how to write the button action for radio button. or suggest any new way].
Each radio button want individual select and deselect. The same process for all the section in tableview. how is possible help me. Thanks advance.
my code:
var radioControllerChoice : SSRadioButtonsController = SSRadioButtonsController()
var radioControllerDip : SSRadioButtonsController = SSRadioButtonsController()
func numberOfSections(in tableView: UITableView) -> Int {
return table_data.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of rows in the section.
return table_data[section].menu_id.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell:CustomiseTableViewCell = tableView.dequeueReusableCell(withIdentifier: "Customise") as! CustomiseTableViewCell
cell.name.text?=table_data[indexPath.section].menu_name[indexPath.row]
print(table_data[indexPath.section].customize[indexPath.row])
switch indexPath.section {
case 2:
radioControllerChoice.addButton(cell.radioBtn)
radioControllerChoice.shouldLetDeSelect = false
case 3:
radioControllerDip.addButton(cell.radioBtn)
radioControllerDip.shouldLetDeSelect = false
switch Int(table_data[indexPath.section].customize[indexPath.row]) {
case 1:
cell.radioBtn.isHidden = false
default:
print("Invalid choose")
cell.radioBtn.addTarget(self, action: #selector(ViewController.didSelectButton), for: .touchUpInside)
cell.radioBtn.tag = indexPath.row
}
}
}
func didSelectButton(selectedButton: UIButton?)
{
/// need solution for button action help me..
}
You can use UIButton instead of SSRadioButton, and then you can change the image of button for checked and unchecked radio button.
Swift3.2:
CustomiseTableViewCell
import UIKit
protocol CustomTableViewCellDelegate {
func didToggleRadioButton(_ indexPath: IndexPath)
}
class CustomiseTableViewCell: UITableViewCell {
#IBOutlet weak var itemLabel: UILabel!
#IBOutlet weak var radioButton: UIButton!
var delegate: CustomTableViewCellDelegate?
func initCellItem() {
let deselectedImage = UIImage(named: "ic_radio_button_unchecked_white")?.withRenderingMode(.alwaysTemplate)
let selectedImage = UIImage(named: "ic_radio_button_checked_white")?.withRenderingMode(.alwaysTemplate)
radioButton.setImage(deselectedImage, for: .normal)
radioButton.setImage(selectedImage, for: .selected)
radioButton.addTarget(self, action: #selector(self.radioButtonTapped), for: .touchUpInside)
}
func radioButtonTapped(_ radioButton: UIButton) {
print("radio button tapped")
let isSelected = !self.radioButton.isSelected
self.radioButton.isSelected = isSelected
if isSelected {
deselectOtherButton()
}
let tableView = self.superview as! UITableView
let tappedCellIndexPath = tableView.indexPath(for: self)!
delegate?.didToggleRadioButton(tappedCellIndexPath)
}
func deselectOtherButton() {
let tableView = self.superview?.superview as! UITableView
let tappedCellIndexPath = tableView.indexPath(for: self)!
let indexPaths = tableView.indexPathsForVisibleRows
for indexPath in indexPaths! {
if indexPath.row != tappedCellIndexPath.row && indexPath.section == tappedCellIndexPath.section {
let cell = tableView.cellForRow(at: IndexPath(row: indexPath.row, section: indexPath.section)) as! CustomiseTableViewCell
cell.radioButton.isSelected = false
}
}
}
}
Call initCellItem method from UITableViewDataSource's delegate method:
// Your ViewController
let menuList = [ ["Cheese", "Bacon", "Egg"],
["Fanta", "Lift", "Coke"] ] // Inside your ViewController
var selectedElement = [Int : String]()
func didToggleRadioButton(_ indexPath: IndexPath) {
let section = indexPath.section
let data = menuList[section][indexPath.row]
if let previousItem = selectedElement[section] {
if previousItem == data {
selectedElement.removeValue(forKey: section)
return
}
}
selectedElement.updateValue(data, forKey: section)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell:CustomiseTableViewCell =
tableView.dequeueReusableCell(withIdentifier: "Customise") as! CustomiseTableViewCell
let item = menuList[indexPath.section][indexPath.row]
cell.itemLabel.text = item
if item == selectedElement[indexPath.section] {
cell.radioButton.isSelected = true
} else {
cell.radioButton.isSelected = false
}
cell.initCellItem()
cell.delegate = self
// Your logic....
return cell
}
Alternate way:
You can use simple UIButton instead of any third party library (SSRadioButton) and use it like:
Set the UIButton's image in default state to - circle (as in the screenshot)
Set the UIButton's image in selected state to - filled circle
UIButton's action event can be captured in a normal way like you do in any other case.
Something like this:
Let me know if you want to follow this approach or need any kind of help regarding this.
I have a xib view in which I took a tableView with a customcell xib. In this custom cell I have a checkbox button which behaves like check and uncheck using custom cell. But when ever I click the first cell checkbox as tick the multiple of 9th cell like 9th row cell, 18th row cell, .....also became ticked. and while scrolling the checkbox tick option is changing between cells. I am not able to know why this is happening..??
I have registered cell xib view as:
override func viewDidLoad() {
super.viewDidLoad()
//Register custom cell
let nib = UINib(nibName: "CustomOneCell", bundle: nil)
AddOnTableView.registerNib(nib, forCellReuseIdentifier: "addoncell")
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return ADDONITEMS.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:CustomOneCell = AddOnTableView.dequeueReusableCellWithIdentifier("addoncell") as! CustomOneCell
let item: AddOnItems = ADDONITEMS[indexPath.row]
cell.addOnName.text = item.name
cell.addOnPrice.text = "£\(item.price!)"
return cell
}
For checkbox I have added a custom class as below:
var isCheckedAddOnGlobal = Bool()
class AddOnCheckBox: UIButton {
let checkedImage = UIImage(named: "checkboxredtick.png")! as UIImage
let unCheckedImage = UIImage(named:"checkbox untick.png")!as UIImage
//bool property
var ischecked:Bool = false{
didSet{
//print(ischecked)
if ischecked == true{
self.setImage(checkedImage, forState: .Normal)
}else{
self.setImage(unCheckedImage, forState: .Normal)
}
}
}
override func awakeFromNib() {
self.addTarget(self, action:#selector(CheckBox.buttonClicked(_:)), forControlEvents: UIControlEvents.TouchUpInside)
self.ischecked = false
}
func buttonClicked(sender: UIButton) {
if (sender == self) {
if ischecked == true{
ischecked = false
isCheckedAddOnGlobal = false
}else{
ischecked = true
isCheckedAddOnGlobal = true
}
}
}
}
This is happening because you are reusing the TableViewCell, To solve your problem you can try something like this, first create an array of Int that give you selected row and use that array inside cellForRowAtIndexPath method.
var selectedItems = [Int]()
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:CustomOneCell = AddO nTableView.dequeueReusableCellWithIdentifier("addoncell") as! CustomOneCell
let item: AddOnItems = ADDONITEMS[indexPath.row]
cell.addOnName.text = item.name
cell.addOnPrice.text = "£\(item.price!)"
cell.checkBoxBtn.tag = indexPath.row
if (selectedItems.contains(indexPath.row)) {
cell.checkBoxBtn.setImage(UIImage(named:"checkbox untick.png"), forState: .Normal)
}
else {
cell.checkBoxBtn.setImage(UIImage(named: "checkboxredtick.png"), forState: .Normal)
}
cell.checkBoxBtn.addTarget(self, action:#selector(self.buttonClicked(_:)), forControlEvents: UIControlEvents.TouchUpInside)
return cell
}
func buttonClicked(sender: UIButton) {
if (self.selectedItems.contains(sender.tag)) {
let index = self.selectedItems.indexOf(sender.tag)
self.selectedItems.removeAtIndex(index)
}
else {
self.selectedItems.append(sender.tag)
}
self.tableView.reloadData()
}
Best way is on selecting cell call
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! CustomOneCell
cell.buttonClicked()
}
and change buttonClicked method to
func buttonClicked() {
...
}
I would make an object, which contains the product information and a boolean, to check if the product has been selected or not.
If you make it this way, the checkmarks will appear correct. When you are scrolling on a tableview, then it loads the data everytime it shows new cells.
Right now, it only knows that the index etc. 9 is selected, and when you scroll down and load new cells, then the index 9 will be selected automatic again.
Try something like this:
Example
class Product {
var productName = "Test"
var isSelected: Bool = false
}
Under your cellForRowAtIndexPath
if product.isSelected == true {
cell.checkBoxBtn.setImage(UIImage(named:"checkbox untick.png"), forState: .Normal)
} else {
cell.checkBoxBtn.setImage(UIImage(named: "checkboxredtick.png"), forState: .Normal)
}