I have a UIButton inside a UITableViewCell where the image changes once the button is tapped. Though the selected buttons get selected as intended, once the UITableView scrolls, the selected images disappear since the cells are reused.
I'm having trouble writing the logic. Please help.
My code is below, in Swift 3.
CellForRow:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
//Button_Action
addSongButtonIdentifier(cell: cell, indexPath.row)
}
This is where the cell is created:
func addSongButtonIdentifier(cell: UITableViewCell, _ index: Int) {
let addButton = cell.viewWithTag(TABLE_CELL_TAGS.addButton) as! UIButton
//accessibilityIdentifier is used to identify a particular element which takes an input parameter of a string
//assigning the indexpath button
addButton.accessibilityIdentifier = String (index)
// print("visible Index:",index)
print("Index when scrolling :",addButton.accessibilityIdentifier!)
addButton.setImage(UIImage(named: "correct"), for: UIControlState.selected)
addButton.setImage(UIImage(named: "add_btn"), for: UIControlState.normal)
addButton.isSelected = false
addButton.addTarget(self, action: #selector(AddToPlaylistViewController.tapFunction), for:.touchUpInside)
}
The tap function:
func tapFunction(sender: UIButton) {
print("IndexOfRow :",sender.accessibilityIdentifier!)
// if let seporated by a comma defines, if let inside a if let. So if the first fails it wont come to second if let
if let rowIndexString = sender.accessibilityIdentifier, let rowIndex = Int(rowIndexString) {
self.sateOfNewSongArray[rowIndex] = !self.sateOfNewSongArray[rowIndex]//toggle the state when tapped multiple times
}
sender.isSelected = !sender.isSelected //image toggle
print(" Array Data: ", self.sateOfNewSongArray)
selectedSongList.removeAll()
for (index, element) in self.sateOfNewSongArray.enumerated() {
if element{
print("true:", index)
selectedSongList.append(songDetailsArray[index])
print("selectedSongList :",selectedSongList)
}
}
}
There is logical error in func addSongButtonIdentifier(cell: UITableViewCell, _ index: Int) function at line addButton.isSelected = false
it should be addButton.isSelected = self.sateOfNewSongArray[index]
Since, cellForRowAtIndexpath method is called every time table is scrolled, it's resetting selected state of 'addButton'
You need to have array where you store which indexes are selected like selectedSongList array that you have. Then in your cellForRow method you need to use bool proparty from this array to give selected or deselected state to your button or in your addSongButtonIdentifier method selected state need to be
addButton.isSelected = selectedSongList.contains(indexPath.row)
Create a Model class for filling UITableView and take UIImage varaivals in that model, which will hold the current image for cell. On click on button action just change the UIImage variable with current image.
Best approach will be using a model class and keeping the track of each indiviual element in cell. But let me give you a quick fix.
Create a custom class of Button any where like this.
class classname: UIButton {
var imageName: String?
}
Go in your storyboard change the class from UIButton to classname
In your tableViewCellForIndexPath
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let addButton = cell.viewWithTag(TABLE_CELL_TAGS.addButton) as! classname
if let imgName = addButton.imageName {
addButton.setImage(UIImage(named: imgName), for: UIControlState.normal)
} else {
addButton.setImage(UIImage(named: "add_btn"), for:UIControlState.normal)
}
addButton.addTarget(self, action: #selector(AddToPlaylistViewController.tapFunction), for:.touchUpInside)
return cell
}
Now let's move to your tapbutton implementation
func tapFunction(sender: classname) {
print("IndexOfRow :",sender.accessibilityIdentifier!)
// if let seporated by a comma defines, if let inside a if let. So if the first fails it wont come to second if let
if let rowIndexString = sender.accessibilityIdentifier, let rowIndex = Int(rowIndexString) {
self.sateOfNewSongArray[rowIndex] = !self.sateOfNewSongArray[rowIndex]//toggle the state when tapped multiple times
}
sender.imageName = sender.imageName == "correct" ? "add_btn" : "correct" //image toggle
sender.setImage(UIImage(named: sender.imageName), for:UIControlState.normal)
print(" Array Data: ", self.sateOfNewSongArray)
selectedSongList.removeAll()
for (index, element) in self.sateOfNewSongArray.enumerated() {
if element{
print("true:", index)
selectedSongList.append(songDetailsArray[index])
print("selectedSongList :",selectedSongList)
}
}
}
Related
I have a tableview, and in each cell I have one button called drop down. So when user presses any option in my drop down - the hidden elements like one more drop down, one name label, one save button will be visible. So again when user presses my save button again those elements will be hidden. Now the issues is when I select my button in two or three cells and if I scroll up and down automatically which and all cell showing the elements that and all getting hide. I need to show which and all cell is clicked and showed the elements.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CartDetailsCell", for: indexPath) as! CartDetailsCell
cell.selectionStyle = UITableViewCellSelectionStyle.none
let notClicked = !selectedIndexPaths.contains(indexPath)
print(notClicked)
cell.noOfQtyOuterView.isHidden = notClicked
cell.saveDataButnOtlet.isHidden = notClicked
cell.noOfQtyButnOutlet.isHidden = notClicked
}
#IBAction func dropDownButnClick(_ sender: Any) {
guard let button = sender as? UIButton else {
return
}
let indexPath = IndexPath(item: button.tag, section: 0)
let cell = self.tableView.cellForRow(at: indexPath) as! CartDetailsCell
dropDown.anchorView = button
dropDown.dataSource = ["Edit", "Cancel"]
dropDown.selectionAction = { [unowned self] (index: Int, item: String) in
switch index {
case 0:
cell.noOfQtyOuterView.isHidden = false
cell.saveDataButnOtlet.isHidden = false
cell.noOfComboOuterViewButn.isHidden = false
case 2:
}
}
Once the button is hidden it will never be un-hidden until you explicitly make it unhidden.
"Now the issues is when i select my button in two or three cells and if i scroll up and down automatically which and all cell showing the elements that and al getting hide"
let cell = tableView.dequeueReusableCell(withIdentifier: "CartDetailsCell", for: indexPath) as! CartDetailsCell
As you are using the cell with the hidden button is reused, it will make the button remain hidden for remaining cells
I suggest to use following pattern, will save you time and you'll have a more reusable and pretty code:
protocol CartDetailsCellDelegate: class {
func didTouchDropDownButton(in cell: CartDetailsCell)
....
}
final class CartDetailsCell: UITableViewCell {
....
weak var delegate: CartDetailsCellDelegate?
#IBAction func didTouchDropDownButton(_ sender: UIButton) {
delegate?.didTouchDropDownButton(in: self)
}
...
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
...
cell.delegate = self
...
}
extension ViewController: CartDetailsCellDelegate {
func didTouchDropDownButton(in cell: CartDetailsCell) {
// Do your stuff here, you have the cell, don't have to play with tags
}
}
My question:
I wanna perform segue to two different destinations in a table view cell. One is directing destination "A" by clicking the cell which handled by didSelectRowAt, Another one is directing to destination "B" by clicking the button inside the cell, for example, the labelName.
For destiation "B", I have try to use (btn.addTarget), but it cannot add parameters, so the destinatin view controller do not know the which cell was being clicked.
Any suggestion?
You can Get the indexPath of your selected TableviewCell in your btnClick function by using this.
let index = self.tableview.indexPathForSelectedRow
Now by doing this you can solve this problem you mentioned.
the destinatin view controller do not know the which cell was being clicked
you can use button.tag and assign the tag of every button with your indexpath.row
button.tag = indexPath.row
button.addTarget(self, action: "btnClick:", forControlEvents: UIControlEvents.TouchUpInside)
func btnClick(sender:UIButton) {
let buttonRow = sender.tag
}
for swift 4 :
button.addTarget(self, action: #selector(händlerButtonTapped), for: .touchUpInside)
#objc func händlerButtonTapped(sender:UIButton) {
let buttonRow = sender.tag
}
Here is my cellForRowAt:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! contentTableViewCell
let content: contentModel
content = contentList[indexPath.row]
contentViewController().getUserProfilePic(userID:content.userID!)
cell.detailLabelName.text = content.name
cell.detailLabelTime.text = content.time
cell.detailTextViewContent.text = content.content
if UserDefaults.standard.data(forKey:content.userID!) != nil {
cell.detailImageSelfie.image = UIImage(data:UserDefaults.standard.data(forKey:content.userID!)!)
} else {
cell.detailImageSelfie.image = #imageLiteral(resourceName: "defaultIcon")
}
return cell
}
I have a UITableView that has sections (Category0, Category1,..), and every row of a specific section is a UITableView that has one section which is the question (Question1,..) and rows which are the options to be answered (option1, option2,..).
The problem is when I click on a button in a specific category and a specific question (Category0, question1, option0) see screenshot1,
immediately another buttons in another categories are clicked (Category1, question2, option0) see screenshot2,
and (Category4, question1, option0) see screenshot3.
the code below:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as? insideTableViewCell
cell?.answerlabel.text = "option \(indexPath.row)"
cell?.initCellItem(id: (myObject?.id)! , answer: (myObject?.answerArray![indexPath.row] as? String)!)
return cell!
}
In a custom UITableViewCell which is insideTableViewCell:
func initCellItem(id: Int , answer: String) {
radioButton.setImage( imageLiteral(resourceName: "unchecked"), for: .normal)
radioButton.setImage( imageLiteral(resourceName: "checked"), for: .selected)
radioButton.tag = id
radioButton.setTitle(answer, for: UIControlState.disabled)
radioButton.addTarget(self, action: #selector(self.radioButtonTapped), for: .touchUpInside)
}
#objc func radioButtonTapped(_ radioButton: UIButton) {
print(radioButton.tag)
print(radioButton.title(for: UIControlState.disabled) as Any)
let answer = radioButton.title(for: UIControlState.disabled) as Any
let StrId = String(radioButton.tag)
defaults.set(answer, forKey: StrId)
let isSelected = !self.radioButton.isSelected
self.radioButton.isSelected = isSelected
if isSelected {
deselectOtherButton()
}
}
func deselectOtherButton() {
let tableView = self.superview as! UITableView
let tappedCellIndexPath = tableView.indexPath(for: self)!
let section = tappedCellIndexPath.section
let rowCounts = tableView.numberOfRows(inSection: section)
for row in 0..<rowCounts {
if row != tappedCellIndexPath.row {
let cell = tableView.cellForRow(at: IndexPath(row: row, section: section)) as! insideTableViewCell
cell.radioButton.isSelected = false
}
}
}
You haven't posted code still guessing.
You can create model object like
class QuestionData {
var strQuestion:String? // This may contains Question
var strOptions:[String]? // It may contains options titles of your buttons
var selectedAnswerIndex:Int? // When any button tapped
}
And you should create category models like
class Categories {
var categoryTitle:String?
var questions:[QuestionData] = []
}
you can use this Categories class as main source of your dataSource array
var arrayDataSource = [Categories]()
And fill this with your original data.
now whenever any button tapped you can use selectedAnswerIndex:Int to store current selected option for question. and if it is null then user has not selected any option yet.
I have created class so it is reference type you can directly set the value without worry
Hope it is helpful to you
There has some and simple code I think it will help you :- if it is not sutable for you pls don't mind :-
if (!btnGreen3.isSelected)
{
btnGreen3.isSelected = !btnGreen3.isSelected
}
btnBlue3.isSelected = false
btnBlack3.isSelected = false
You need to save the states of every cell.
The reason is you are using dequereuseable cell with identifier when you scroll it switch to another cell.
So make Array or Dictionary where save the state of every selected and unselected Rows.
I have a problem about my cell's button.
In my tableView each row is composed by: an image, some labels and a button.
The button has a checkmark image. When it is clicked, the button's image changes.
The problem is that also another button's image changes without reason.
This mistake happens because my cell is reused.
I have tried to use prepareForReuse method in TableViewCell but nothing happens. I've also tried with selectedRowAt but I didn't have any results. Please help me.
Image 1:
Image 2:
This is my func in my custom Cell:
override func prepareForReuse() {
if checkStr == "uncheck"{
self.checkBook.setImage(uncheck, for: .normal)
} else if checkStr == "check"{
self.checkBook.setImage(check, for: .normal)
}
}
func isPressed(){
let uncheck = UIImage(named:"uncheck")
let check = UIImage(named: "check")
if self.checkBook.currentImage == uncheck{
checkStr == "check"
} else self.checkBook.currentImage == check{
checkStr == "uncheck"
}
}
In my tableView:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedCell: ListPropertyUserCell = tableView.cellForRow(at: indexPath) as! ListPropertyUserCell
let uncheck = UIImage(named:"uncheck")
let check = UIImage(named: "check")
if selectedCell.checkBook.imageView?.image == uncheck{
selectedCell.checkStr = "check"
} else if selectedCell.checkBook.imageView?.image == check{
selectedCell.checkStr = "uncheck"
}
}
From the information in your post, this looks like a cell reuse issue. The problem is that the tableView reuses the cells rather than creating new ones, to maintain performance. If you haven't reset the cell's state, the reused cell will be remain configured in the old state.
For a quick fix, you can implement the prepareForReuse method on UITableViewCell.
However, you'll need to store which cell is 'checked' in your view controller if you want the checkbox to be selected after scrolling the tableView. You can store this yourself, or use the tableView's didSelectRowAtIndexPath method.
Try to do it like this:
var checkBook = UIImageView()
if self.checkBook.image == UIImage(named: "check"){
self.checkBook.image = UIImage(named: "uncheck")
}
else{
self.checkBook.image = UIImage(named: "check")
}
If you're using the click on the entire cell, you can override the setSelected func in your custom cell just like that.
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if selected {
self.checkBook.image = UIImage(named: "check")
} else {
self.checkBook.image = UIImage(named: "uncheck")
}
}
UITableViewCell is reusable. You can't store state of view in cell. You should setup cell in
func tableView(UITableView, cellForRowAt: IndexPath)
method of your data source
The easiest way to achieve that is to implement
func tableView(UITableView, didSelectRowAt: IndexPath)
func tableView(UITableView, didDeselectRowAt: IndexPath)
methods of UITableViewDelegate
Then you can add/remove indexPath to some array in these methods and in cellForRowAtIndexPath setup cell.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("YourTableViewCell") as! YourTableViewCell
if array.contains(indexPath) {
cell.checkBook.image = UIImage(named: "check")
} else {
cell.checkBook.image = UIImage(named: "uncheck")
}
return cell
}
Try my code . here selectindex is use for get selected cell index and selectedindex is NSMutableArray that i store all selected cell value.
var selectindex : Int?
var selectedindex : NSMutableArray = NSMutableArray()
#IBOutlet var tableview: UITableView!
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("LikeCell", forIndexPath: indexPath)
let like: UIButton = (cell.viewWithTag(2) as! UIButton)// like button
let comment: UIButton = (cell.viewWithTag(3) as! UIButton) // comment button
comment.setBackgroundImage(UIImage(named: "chat.png"), forState: UIControlState.Normal) // comment button set
like.addTarget(self, action: #selector(self.CloseMethod(_:event:)), forControlEvents: .TouchDown)
comment.addTarget(self, action: #selector(self.CloseMethod1(_:event:)), forControlEvents: .TouchDown)
return cell
}
// This is my like button action method.
#IBAction func CloseMethod(sender: UIButton, event: AnyObject) {
let touches = event.allTouches()!
let touch = touches.first!
let currentTouchPosition = touch.locationInView(self.tableview)
let indexPath = self.tableview.indexPathForRowAtPoint(currentTouchPosition)!
selectindex = indexPath.row
if selectedindex.containsObject(selectindex!) {
sender.setBackgroundImage(UIImage.init(named: "like (1).png"), forState: .Normal)
selectedindex.removeObject(selectindex!)
}else{
sender.setBackgroundImage(UIImage.init(named: "like.png"), forState: .Normal)
selectedindex.addObject(selectindex!)
}
}
I faced this problem recently, and did not find much about it. What solve, after much searching, was to use:
override func prepareForReuse() {
btnAdd.setImage(nil, for: .normal) //here I use to change to none image
super.prepareForReuse()
}
just put this method inside your custom UITableViewCell, and set which item you want to realese stats.
I have a TableViewController with a custom cell. When I tap the like button inside one of the cells, it is causing at least one other cell to have the like button tapped.
I am using Parse, and it is not affecting the actual like count of the second one which is being ghost tapped, but it is disabling the like button and turning it red.
I have read about cell reuse, and similar topics but am completely lost. I am new to swift, and if someone could help me navigate how to fix this, I can't find a solution on SO about Swift, and Parse.
TableViewController
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:ChinTwoTableViewCell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! ChinTwoTableViewCell
cell.selectionStyle = .None
// Configure the cell...
let chinTwo:PFObject = self.timelineData.objectAtIndex(indexPath.row) as! PFObject
var myVar:Int = chinTwo.objectForKey("likeCount") as! Int
cell.countLabel.text = String(myVar)
cell.nameLabel.text = chinTwo.objectForKey("name") as? String
cell.bodyText.text = chinTwo.objectForKey("body") as! String
cell.bodyText.font = UIFont(name: "HelveticaNeue-UltraLight", size: 18)
cell.bodyText.textAlignment = .Center
cell.likeButton.tag = indexPath.row;
cell.likeButton.addTarget(self, action: "likeButtonTapped:", forControlEvents: .TouchUpInside)
return cell
}
#IBAction func likeButtonTapped(sender: AnyObject) {
let chinTwo = self.timelineData[sender.tag] as! PFObject
chinTwo["likeCount"] = (chinTwo["likeCount"] as! Int) + 1
sender.setTitleColor(UIColor.redColor(), forState: UIControlState.Normal)
chinTwo.saveInBackgroundWithBlock {
(success: Bool, error: NSError?) -> Void in
if (success) {
println("Worked")
} else {
println("Didn't Work")
}
}
self.tableView.reloadData()
}
TableViewCell
#IBAction func likeTapped(sender: AnyObject) {
likeButton.enabled = false
}
The same problem occurs with the report button.
Due to reuseable cells, the same likeButton will be used on multiple cells depending on if it's shown or not. If you change the color of one instance it will keep that color when it is reused again for another cell. Instead of setting the color in the click method you should determine if the button should be red or not in the cellForRowAtIndexPath method. So something like:
var likedRows: Set<Int> = Set()
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
...
self.markButtonIfLiked(cell.button, atRow: indexPath.row)
...
}
#IBAction func likeButtonTapped(button: UIButton) {
...
self.likedRows.insert(button.tag)
self.markButtonIfLiked(button, atRow: button.tag)
...
}
func markButtonIfLiked(button: UIButton, atRow row: Int) {
if (self.likedRows.contains(row)) {
button.setTitleColor(.redColor(), forState: .Normal)
}
}
And you shouldn't need the tableView.reloadData() call.