I have a collection view and inside a button.
When I tap this button I want to change its name.
I managed to do it but when I tap it, it changes if I have 20 buttons 10 of them like this if I tap the first button it changes the 0,2,4,6,8 and if I tap a button which isn't in the list then all buttons are checked.
This is my code:
#IBAction func following(sender: AnyObject) {
if follow.tag == sender.tag {
follow.setTitle("Following", forState: .Normal)
}
print("\(follow.tag) \(sender.tag)")
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Interest Cell", forIndexPath: indexPath) as! ThirdTabCell
cell.follow.tag = indexPath.row
return cell
}
Inside button's function I tried without if statement too but again the same problem.
When I print the tags it prints only the button which was tapped.
I also tried this to my ViewController
cell.follow.addTarget(self, action: #selector(ThirdTab.follow(_:)), forControlEvents: UIControlEvents.TouchUpInside)
//inside cellForItemAtIndexPath
func follow(sender:UIButton!) {
}
Just a short overview, So you get your answer
UICollectionView is highly optimized, and thus only keep On-screen visible rows in memory. Now, All rows Cells are cached in Pool and are reused and not regenerated. Whenever, user scrolls the UICollectionView, it adds the just-hidden rows in Pool and reuses them for next to be visible rows.
So, now, coming to your answer
When you tap on button, its title will get updated, but when you will scroll your collection view, the same cell with "updated button text" will be reused and that will cause the issue you are seeing.
SOLUTION
SAVE button state in an array, in your action method
#IBAction func following(sender: AnyObject) {
if follow.tag == sender.tag {
array[sender.tag] = "<text>"
collectionView.reloadData()
}
print("\(follow.tag) \(sender.tag)")
}
and inside your datasource method
update your button text like below:
//trick is to update your button text for each index
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell{
//SAMPLE CODE
let buttonValue = array[indexPath.index]
// update button value for each index
//trick is to update your button text for each index
cell.button.setTitle("", forState: .Normal)
}
Please change below the set title line in following function.
Existing Line:
follow.setTitle("Following", forState: .Normal)
New Line:
follow.setTitle("Following", forState: UIControlState.Normal)
Hope this is working for you.
Thanks
I would suggest you to approach it this way.
Inside cellForItemAtIndexPath you can assign a buttons outlet like this
cell.followOutlet.tag = indexPath.row
and then inside your follow function you can do this
#IBAction func following(sender: AnyObject) {
let follow = sender as! UIButton
let indexP = NSIndexPath(forRow: follow.tag, inSection: 0)
let cell = yourCollectionView.cellForItemAtIndexPath(indexP) as! yourCellName
follow.setTitle("Following", forState: .Normal)
}
And now you can do whatever you want with your button.
Related
I have a uicollectionview with a series of custom class cells that have a few textviews and a uibutton. With over 100 cells, I just want to toggle the uibutton image for each respective cell. The uibutton is a favorites button, and like most apps I just want to favorite and "un-favorite" different cells.
NOTE: I tried to add the gesture recognizer in the class directly, but for some reason the image changes, but it highlights multiple cells instead of the specific cell that was clicked
my code:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! SimpleExampleSubCell
cell.backgroundColor = UIColor.init(white: 0.10, alpha: 0.25)
cell.infoLine2TextVw.text = ""
cell.infoLine3TextVw.text = ""
if let heading_name = self.dict_dict_holder[indexPath.item]["Name"]{
cell.headerTextVw.text = heading_name
cell.infoLine1TextVw.text = self.dict_dict_holder[indexPath.item]["Phone"]
}
cell.bringSubview(toFront: cell.headerTextVw)
cell.favorite_button.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(AddFavorite(withSender:))))
return cell
}
#objc func AddFavorite(withSender sender:UIButton){
print("clicked")
//The line below fails each time I run it.
sender.setImage(newImage.png,.normal)
}
Replace
#objc func addFavorite(withSender sender:UIButton){
with
// not recommended use touchUpInside
#objc func addFavorite(_ sender:UITapGestureRecognizer){
let btn = sender.view! as! UIButton
}
OR better
cell.favorite_button.addTarget(self, action:#selector(addFavorite), for: .touchUpInside)
Don't Add tapgestures to buttons , as they they have their own targets like touchUpInside or touchUpOutside and many more
table cells are reused you need to nil them inside cellForRowAt or give an else
if someCondition {
cell.favorite_button.setImage(newImage1.png,.normal)
else {
cell.favorite_button.setImage(newImage2.png,.normal)
}
you have to set the default image (plus everything you want to reset) for each cell in the prepareForReuse() method so it clears up the reused content
I am trying to implement play/pause button in tableview cell. each cell having single button, whenever user click it, It should change button image also need to call required function, also after scroll it should same.
Below code I am using
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) - > UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("productCell") as ? SepetCell
cell.onButtonTapped = {
//Do whatever you want to do when the button is tapped here
}
See first of all every button of the tableView Cell will have a unique tag associated with it, so in order to update the button of a particular cell, you will have to define the tag of a button in the cells and then pass this tag to your function to perform action on that particular button of the selected cell.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell_identifier", for:
indexPath) as! CellClass
cell.button.tag = indexPath.row
cell.button.addTarget(self, action: #selector(playpause), for: .touchUpInside)
}
#objc func playpause(btn : UIButton){
if btn.currentImage == UIImage(named: "Play") {
btn.setImage(UIImage(named : "Pause"), forState: .Normal)
}
else {
btn.setImage(UIImage(named : "Play"), forState: .Normal)
}
// perform your desired action of the button over here
}
State of the art in Swift are callback closures. They are easy to implement and very efficient.
In the data source model add a property
var isPlaying = false
In Interface Builder select the button in the custom cell and press ⌥⌘4 to go to the Attributes Inspector. In the popup menu State Config select Default and choose the appropriate image from Image popup, Do the same for the Selected state.
In the custom cell add a callback property and an outlet and action for the button (connect both to the button). The image is set via the isSelected property.
#IBOutlet weak var button : UIButton!
var callback : (()->())?
#IBAction func push(_ sender: UIButton) {
callback?()
}
In the controller in cellForRow add the callback, item is the current item of the data source array. The state of the button is kept in isPlaying
cell.button.isSelected = item.isPlaying
cell.callback = {
item.isPlaying = !item.isPlaying
cell.button.isSelected = item.isPlaying
}
What I'm trying to do is when I tap the button in a cell, that button in that cell becomes invisible. The problem is when I tap the button, it becomes invisible, but when I scroll the collection view the hidden button goes from one to the other. For example, I tap the second one it hides but when I scroll I see that the 7th becomes hidden. Every time I scroll the hidden button change.
This is the code I wrote:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell : CollectionViewCellKharid3 = collectionView.dequeueReusableCell(withReuseIdentifier: "customcell3", for: indexPath) as! CollectionViewCellKharid3
cell.lblEsmeMahsul.text = mainCats[indexPath.row]
cell.imgMahsul.af_setImage(withURL: URL(string : (mainadress + "/Opitures/" + mainPicNumbers[indexPath.row]))!, placeholderImage: UIImage(named: "loadings" ))
cell.btnKharid.addTarget(self, action: #selector(btnColectionviewCellTapped), for : UIControlEvents.touchUpInside)
cell.btnKharid.tag = indexPath.row
cell.btnMosbat.addTarget(self, action: #selector(btnMosbatTapped), for : UIControlEvents.touchUpInside)
cell.btnMosbat.tag = indexPath.row
cell.configureCell()
return cell
}
#objc func btnColectionviewCellTapped(_ sender:UIButton){
// let indexPath : IndexPath = self.collectionview1.ind
print(sender.tag)
sender.isHidden = true
}
#objc func btnMosbatTapped(_ sender:UIButton){
let index = IndexPath(item: sender.tag , section: 0)
let cell = self.collectionviewForushVije.cellForItem(at: index) as? CollectionViewCellKharid3
cell?.lblTedad.text = "22"
print(sender.tag)
}
Cells get reused. You need to keep track of which cells have been tapped so you can set the proper button state in your cellForItemAt method.
Declare a property in your class:
var beenTapped: Set<Int> = []
Then in btnColectionviewCellTapped add:
beenTapped.insert(sender.tag)
And in cellForItemAt you need:
cell.btnKharid.isHidden = beenTapped.contains(indexPath.item)
You should also replace the use of indexPath.row with indexPath.item. row is for table views. item is for collection views.
It's a very common mis-use of UICollectionView(or UITableView). When deal with them, you should alway keep one thing in mind, re-use. The collection/tableview cell will be highly reuse by os when on need. The problem cause in your code is, you assume the one time set of one property in a cell will be persistence, which is wrong. The cell come from dequeue method, can always be a new cell or an existing cell, therefore, any configuration should be apply to a cell should be config again. Think in that way, all view in a cell is "dirty" when it get it from collection view, you should set the property you want before return it back(or have a mechanism to set it later). Therefore, in your case, just set the isHidden property every time you prepare the cell in cellForRow delegate.
I have 2 VCs, one of them is called HomeVC the other is DetailVC. I have a table view on HomeVC which displays cells with a label and a button. DetailVC just has a label. I am displaying an array of strings on the table view and when the button on the cell is clicked i want to carry the text in the label to the DetailVC's label.
Now i can easily do this with either didSelectRowAt method or using indexPathForSelectedRow in prepare segue method. But both cases requires me to tap on the cell itself but not the button.
I am just a beginner in swift. But to explain this there shouldn't be need for much code. So if you can, please explain with detail.
Thanks in advance.
In cellForRowAt add target to button i.e
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellID", for: indexPath)
cell.button.tag = indexPath.row
cell.button.addTarget(self, action: Selector("buttonAction:"), for: .touchUpInside)
// other cell element setup
return cell
}
And at button action get the item from array using button tag i.e
func buttonAction(sender: UIButton) {
let data = tableArray[sender.tag]
// logic to pass present detailVC
}
Hope this will work!!
If you are using collection view you can use the following
// Set The Click Action On Button
cell.bProfileImage.addTarget(self, action: #selector(connected(sender:)), for:
.touchUpInside)
cell.bProfileImage.tag = indexPath.row
Then in your function
// Function For TouchUpInside For Cell
#objc func connected(sender: UIButton) {
let data = individualChatsListArray[sender.tag]
print(data.name)
}
I currently am using a Collection View to display a list of events to a user, and each one of my custom cells has a button that invites the user to attend the event. When pressed, the button image should then change to a newImage.png which displays that they are now attending that event. When I do this in my code below, pressing the button does in fact change the picture, but as I scroll down my collection view, multiple cells that have yet to be clicked also have changed to the "newImage.png." How can I stop this from happening?
class CustomCell: UICollectionViewCell{
#IBAction func myButtonAction(sender: UIButton) {
myButtonOutlet.setImage(UIImage(named: "newImage.png"), forState: UIControlState.Normal)
}
#IBOutlet weak var myButtonOutlet: UIButton!
}
The collection view is reusing cells, as it is designed to do. What you should do is reset the image in your cellForItemAtIndexPath implementation.
I've had this issue before too and this is most likely do to cell reuse. What you might try to do to avoid this problem is to explicitly set the cell's image in your cellForItemAtIndexPath() and then add something to your model that keeps track of which events the user is attending. Then, again in your cellForItemAtIndexPath(), check the model to see what button should be on that cell, and then change it accordingly.
You need to store selected button index in your class and check perticular index in your function
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! UICollectionViewCell
cell.backgroundColor = UIColor.blackColor()
if(selectedIndex == indexPath.row){
myButtonOutlet.setImage(UIImage(named: "newImage.png"), forState: UIControlState.Normal)
//change background image here also your button code
}
return cell
}
and after doing this steps . Reload collection view .
This ended up solving my problem. I have an array that I store my Cells in. Each cell has a boolean called isAttending. In my cellForItemAtIndexPath method, I implemented the code below along with the function switchImage:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
var cell = collectionView.dequeueReusableCellWithReuseIdentifier("CalendarCell", forIndexPath: indexPath) as! CalendarCell
cell.customButton.layer.setValue(indexPath.row, forKey: "index")
cell.customButton.addTarget(self, action: "switchImage:", forControlEvents: UIControlEvents.TouchUpInside)
return cell
}
func switchImage(sender: UIButton){
let index : Int = (sender.layer.valueForKey("index")) as! Int
if (events[index].isAttending == false){
events[index].isAttending = true
cell.customButton.setImage(UIImage(named: "isAttending.png"), forState: UIControlState.Normal)
}else{
events[index].isAttending = false
cell.customButton.setImage(UIImage(named: "isNotAttending.png"), forState: UIControlState.Normal)
}
self.collectionView.reloadData()
}