How can I access a button added in a collection view cell? - ios

I added buttons in my collection view cells by the code below, where 'myButton' refers to the buttons I try to make access to.
When I click some button outside the collectionView, I want one of my buttons to have its background image changed to have the background of the button clicked, which I tried with 'sendToBox' function below;
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! ItemCollectionViewCell
cell.myButton.setTitle(self.items[indexPath.item], for: .normal)
cell.backgroundColor = UIColor.white
cell.layer.borderColor = UIColor.black.cgColor
cell.layer.borderWidth = 0.5
return cell
}
...
#IBAction func keyClick(_ sender: UIButton) {
...
sendToBox(object: sender)
...
}
...
func sendToBox(object sentObject: UIButton) {
let imageToSend = sentObject.backgroundImage(for: .normal)
let imageIdentity = sentObject.restorationIdentifier
let cell = self.collectionView(ItemCollectionSet, cellForItemAt: IndexPath(item: 0, section: 0))
cell.myButton ## blah blah not working!
}
So I want to make a direct access to one of the cells I created, and then change the button inside it to have a different appearance. I'm stuck in this matter for a whole day, please help me out.

Related

Collection view button background image getting repeated on scroll

I have a collection view.I have a button called furnitureSelectionBtn inside the collection view cell.
When i tap the button inside the collection view,it sets the button image as selected.png.When i tap it again it gets unselected and i set with unselected.png image.
When i tap the button cardselection method gets triggered and i save the index clicked in allFurnituresArray.
Now when i scroll this collection view ,few of the cells are getting selected image even if i havent selected them.This generally happens for the cells outside of the view .Cells getting reused I beleive.
For example if i have selected the 1st button and i start scrolling,I see 4th button also in selected state
So i tried to check in cellforRow method against the allFurnituresArray indexes which are selected and even thats not working.
I also tried to see if i can use prepareForReuse() but we dont get information about index I have scrolled to there.
Can someone please help
CardCollectionViewCell.swift
class CardCollectionViewCell: SwipingCarouselCollectionViewCell {
#IBOutlet weak var furnitureSelectionBtn: UIButton!
static let reuseIdentifier = "CardCollectionViewCell"
static var nib: UINib {
get {
return UINib(nibName: "CardCollectionViewCell", bundle: nil)
}
}
override func awakeFromNib() {
super.awakeFromNib()
furnitureSelectionBtn.setBackgroundImage(image, for: UIControl.State.normal)
}
CardViewController.swift
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CardCollectionViewCell.reuseIdentifier, for: indexPath) as! CardCollectionViewCell
// Configure the cell
cell.delegate = self
cell.furnitureSelectionBtn.tag = (indexPath as NSIndexPath).row
for element in allFurnituresArray {
print(element)
if (indexPath.row) == element as! Int {
let image1 = UIImage(named: "selected.png")
cell.furnitureSelectionBtn.setBackgroundImage(image1, for: UIControl.State.selected)
}
else
{
let image1 = UIImage(named: "unselected.png")
cell.furnitureSelectionBtn.setBackgroundImage(image1, for: UIControl.State.normal)
}
}
cell.furnitureSelectionBtn.addTarget(self, action: #selector(CardViewController.cardselection(_:)), for: .touchUpInside)
return cell
}
#objc func cardselection(_ sender:UIButton!)
{
if sender.isSelected == true {
sender.isSelected = false
let image1 = UIImage(named: "unselected.png")
sender.setBackgroundImage(image1, for: UIControl.State.normal)
allFurnituresArray.remove(sender.tag)
}else {
sender.isSelected = true
let image1 = UIImage(named: "selected.png")
allFurnituresArray.add(sender.tag)
sender.setBackgroundImage(image1, for: UIControl.State.selected)
}
print("Button tapped")
print(sender.tag)
}
The best way to save the selection status is to add this data to your item in the datasource array, e.g,
(1) datasource array
var items = [Item]()
(2) didSelectItem...
items[indexPath.row].isSelected = !(items[indexPath.row].isSelected)
and handle selection status in your cell.

Toggle image within uicollectionviewcell when clicked

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

Change a view inside of UICollectionViewCell when tapped not working

I have a list of user avatars inside of a UICollectionViewCell. When the user taps on one, I'd like to add the selected item to a collection as well as highlight it to indicate it's been tapped.
Unfortunately the UI doesn't seem to update. Any ideas?
Initially I load the images and set them to be rounded. I can even set the border color, if I want, but for now I set it to clear. This all works upon loading the cells:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath as IndexPath) as! UICollectionViewCell
// Configure the cell
let member: UserProfile = groupMembers[indexPath.item]
let imgAvatar = cell.viewWithTag(2) as! UIImageView
imgAvatar.layer.cornerRadius = contactAvatar.frame.size.width / 2
imgAvatar.clipsToBounds = true
imgAvatar.contentMode = UIViewContentMode.scaleAspectFill
imgAvatar.layer.borderWidth = 2.0
imgAvatar.layer.borderColor = UIColor.clear.cgColor
imgAvatar.layer.cornerRadius = 30.0
let downloadURL = NSURL(string: member.avatarUrl)!
imgAvatar.af_setImage(withURL: downloadURL as URL)
return cell
}
And now here is the code that executes when you tap on any given UIImageView in the collection, but it does not seem to update the image:
///Fired when tapped on an image of a person
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath as IndexPath) as! UICollectionViewCell
let tappedUser: UserProfile = groupMembers[indexPath.item]
let imgAvatar = cell.viewWithTag(2) as! UIImageView
..add item to collection, etc..
//Update imageview to indicate it's been tapped.
DispatchQueue.main.async {
contactAvatar.layer.cornerRadius = contactAvatar.frame.size.width / 2
imgAvatar.clipsToBounds = true
imgAvatar.contentMode = UIViewContentMode.scaleAspectFill
imgAvatar.layer.borderWidth = 2.0
imgAvatar.layer.cornerRadius = 30.0
imgAvatar.layer.borderColor = UIcolor.blue.cgColor
}
}
}
Running this code, it hits the breakpoint to indicate I've tapped on the item, but it does not update the UI. I'm convinced there is a thread / ui issue where the collection view isn't "redrawing" the changes I've made to the image. Maybe I can't change around the appearances of a view inside of a collection?
Thanks in advance for any insight.
Your didSelect is not correct. Get the cell from the collection view.
cellForItem
///Fired when tapped on an image of a person
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at:indexPath) as! UICollectionViewCell
let tappedUser: UserProfile = groupMembers[indexPath.item]
let imgAvatar = cell.viewWithTag(2) as! UIImageView
..add item to collection, etc..
//Update imageview to indicate it's been tapped.
DispatchQueue.main.async {
contactAvatar.layer.cornerRadius = contactAvatar.frame.size.width / 2
contactAvatar.clipsToBounds = true
contactAvatar.contentMode = UIViewContentMode.scaleAspectFill
contactAvatar.layer.borderWidth = 2.0
contactAvatar.layer.cornerRadius = 30.0
contactAvatar.layer.borderColor = UIcolor.blue.cgColor
}
}
}
Using mobile so let me know if it does not work.
The code which you have written to to get instance of cell is not correct please use below line of code and rest all seems correct hope by changing this line will work.
let cell = collectionView.cellForItem(at:indexPath) as! UICollectionViewCell

Weird behavior when scrolling and selecting in UICollectionView

I am having issues with displaying a checkmark on the a custom cell in a UICollectionView. For the first few taps everything works as expected, but when I begin scrolling or tapping repeatedly or click on the already selected cell, the behavior becomes odd as shown in the gif. Perhaps I am going about this in an incorrect way? The .addCheck() and .removeCheck() are methods inside the custom UICollectionViewCell class I made and all they do is add a checkmark image or remove one from the cell view. The odd behavior shown here
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! ColorUICollectionViewCell
// Configure the cell
let color = colorList[(indexPath as NSIndexPath).row]
cell.delegate = self
cell.textLabel.text = color.name
cell.backgroundColor = color.color
if color.selected {
cell.addCheck()
}
else {
cell.removeCheck()
}
return cell
}
// user selects item
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// set colors to false for selection
for color in colorList {
color.selected = false
}
// set selected color to true for selection
let color = colorList[indexPath.row]
color.selected = true
settings.backgroundColor = color.color
//userDefaults.set(selectedIndex, forKey: "selectedIndex")
collectionView.reloadData()
}
Below is what the addCheck() and removeCheck() functions in my custom cell look like.
func addCheck() {
// create check image
let checkImage = UIImage(named: "checkmark")
checkImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: bounds.size.height / 4, height: bounds.size.height / 4))
checkImageView.image = checkImage!.withRenderingMode(UIImageRenderingMode.alwaysTemplate)
checkImageView.tintColor = UIColor.white()
// add the views
addSubview(checkImageView)
}
func removeCheck() {
if checkImageView != nil {
checkImageView.removeFromSuperview()
}
}
first off, you can simplify your didSelect a bit:
override func collectionView(collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// set colors to false for selection
for (index, color) in colorList.enumerate() {
if index == indexPath.row {
color.selected = false
settings.backgroundColor = color.color
}
else {
color.selected = false
}
}
collectionView.reloadData()
}
Based on the language in your cellForItemAt method, I'm guessing you're adding a second check mark image when you tap on the same cell twice, and it's not being tracked properly so that cell just keeps getting rotated around overtime the collectionView's reloaded
Post your cell class, or at least the logic for addCheck and removeCheck and we might find the problem.
What I would recommend is permanently having an imageView with the check mark over the cell, when simple show/hide it based on the selection. This should speed up the collectionView as well.

How to update the button tag which is part of UICollectionViewCell after a cell is deleted in UICollectionView?

Here's a problem which I have been stuck at for quite some time now.
Here's the code
let indexPath = NSIndexPath(forRow: sender.tag, inSection: 0)
collectionViewLove?.performBatchUpdates({() -> Void in
self.collectionViewLove?.deleteItemsAtIndexPaths([indexPath])
self.wishlist?.results.removeAtIndex(indexPath.row)
self.collectionViewLove?.reloadData()}, completion: nil)}
I have a button inside each UICollectionViewCell which deletes it on clicking. The only way for me to retrieve the indexPath is through the button tag. I have initialized the button tag in
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
However every time I delete, the first time it deletes the corresponding cell whereas the next time it deletes the cell follwing the one I clicked. The reason is that my button tag is not getting updated when I call the function reloadData().
Ideally, when I call the reloadData() ,
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
should get called and update the button tag for each cell. But that is not happening. Solution anyone?
EDIT:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
collectionView.registerNib(UINib(nibName: "LoveListCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "Cell")
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! LoveListCollectionViewCell
cell.imgView.hnk_setImageFromURL(NSURL(string: (wishlist?.results[indexPath.row].image)!)!, placeholder: UIImage(named: "preloader"))
let item = self.wishlist?.results[indexPath.row]
cell.layer.borderColor = UIColor.grayColor().CGColor
cell.layer.borderWidth = 1
cell.itemName.text = item?.title
cell.itemName.numberOfLines = 1
if(item?.price != nil){
cell.price.text = "\u{20B9} " + (item?.price.stringByReplacingOccurrencesOfString("Rs.", withString: ""))!
}
cell.price.adjustsFontSizeToFitWidth = true
cell.deleteButton.tag = indexPath.row
cell.deleteButton.addTarget(self, action: "removeFromLoveList:", forControlEvents: .TouchUpInside)
cell.buyButton.tag = indexPath.row
cell.buyButton.backgroundColor = UIColor.blackColor()
cell.buyButton.addTarget(self, action: "buyAction:", forControlEvents: .TouchUpInside)
return cell
}
A couple of things:
You're doing too much work in cellForItemAtIndexPath--you really want that to be as speedy as possible. For example, you only need to register the nib once for the collectionView--viewDidLoad() is a good place for that. Also, you should set initial state of the cell in the cell's prepareForReuse() method, and then only use cellForItemAtIndexPath to update with the custom state from the item.
You shouldn't reload the data until the deletion is complete. Move reloadData into your completion block so the delete method is complete and the view has had time to update its indexes.
However, it would be better if you didn't have to call reloadData in the first place. Your implementation ties the button's tag to an indexPath, but these mutate at different times. What about tying the button's tag to, say, the wishlist item ID. Then you can look up the appropriate indexPath based on the ID.
Revised code would look something like this (untested and not syntax-checked):
// In LoveListCollectionViewCell
override func prepareForReuse() {
// You could also set these in the cell's initializer if they're not going to change
cell.layer.borderColor = UIColor.grayColor().CGColor
cell.layer.borderWidth = 1
cell.itemName.numberOfLines = 1
cell.price.adjustsFontSizeToFitWidth = true
cell.buyButton.backgroundColor = UIColor.blackColor()
}
// In your UICollectionView class
// Cache placeholder image since it doesn't change
private let placeholderImage = UIImage(named: "preloader")
override func viewDidLoad() {
super.viewDidLoad()
collectionView.registerNib(UINib(nibName: "LoveListCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "Cell")
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! LoveListCollectionViewCell
cell.imgView.hnk_setImageFromURL(NSURL(string: (wishlist?.results[indexPath.row].image)!)!, placeholder: placeholderImage)
let item = self.wishlist?.results[indexPath.row]
cell.itemName.text = item?.title
if(item?.price != nil){
cell.price.text = "\u{20B9} " + (item?.price.stringByReplacingOccurrencesOfString("Rs.", withString: ""))!
}
cell.deleteButton.tag = item?.id
cell.deleteButton.addTarget(self, action: "removeFromLoveList:", forControlEvents: .TouchUpInside)
cell.buyButton.tag = item?.id
cell.buyButton.addTarget(self, action: "buyAction:", forControlEvents: .TouchUpInside)
return cell
}
func removeFromLoveList(sender: AnyObject?) {
let id = sender.tag
let index = wishlist?.results.indexOf { $0.id == id }
let indexPath = NSIndexPath(forRow: index, inSection: 0)
collectionViewLove?.deleteItemsAtIndexPaths([indexPath])
wishlist?.results.removeAtIndex(index)
}
It's probably not a good idea to be storing data in the cell unless it is needed to display the cell. Instead your could rely on the UICollectionView to give you the correct indexPath then use that for the deleting from your data source and updating the collectionview.
To do this use a delegate pattern with cells.
1.Define a protocol that your controller/datasource should conform to.
protocol DeleteButtonProtocol {
func deleteButtonTappedFromCell(cell: UICollectionViewCell) -> Void
}
2.Add a delegate property to your custom cell which would call back to the controller on the delete action. The important thing is to pass the cell in to that call as self.
class CustomCell: UICollectionViewCell {
var deleteButtonDelegate: DeleteButtonProtocol!
// Other cell configuration
func buttonTapped(sender: UIButton){
self.deleteButtonDelegate.deleteButtonTappedFromCell(self)
}
}
3.Then back in the controller implement the protocol function to handle the delete action. Here you could get the indexPath for the item from the collectionView which could be used to delete the data and remove the cell from the collectionView.
class CollectionViewController: UICollectionViewController, DeleteButtonProtocol {
// Other CollectionView Stuff
func deleteButtonTappedFromCell(cell: UICollectionViewCell) {
let deleteIndexPath = self.collectionView!.indexPathForCell(cell)!
self.wishList.removeAtIndex(deleteIndexPath.row)
self.collectionView?.performBatchUpdates({ () -> Void in
self.collectionView?.deleteItemsAtIndexPaths([deleteIndexPath])
}, completion: nil)
}
}
4.Make sure you set the delegate for the cell when configuring it so the delegate calls back to somewhere.
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
//Other cell configuring here
var cell = collectionView.dequeueReusableCellWithReuseIdentifier("identifier", forIndexPath: indexPath)
(cell as! CustomCell).deleteButtonDelegate = self
return cell
}
}
I was facing the similar issue and I found the answer by just reloading collection view in the completion block.
Just update your code like.
let indexPath = NSIndexPath(forRow: sender.tag, inSection: 0)
collectionViewLove?.performBatchUpdates({
self.collectionViewLove?.deleteItemsAtIndexPaths([indexPath])
self.wishlist?.results.removeAtIndex(indexPath.row)
}, completion: {
self.collectionViewLove?.reloadData()
})
which is mentioned in UICollectionView Performing Updates using performBatchUpdates by Nik

Resources