didDeselectItemAt not working after scrolling swift - swift

I am designing a menu tab bar with collectionview, and I want to change the color of the label when it is selcted.
Everything works fine but when the selected item is not in the screen anymore(due to scroll out of the screen), then the func inside didDeselectItemAt is not working anymore.
Is there anyway to solve this problem? Below is the code:
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
if collectionView == self.productMenuCollectionView {
guard let cell = self.productMenuCollectionView.cellForItem(at: indexPath) as? ProductMenuCollectionViewCell else {
return
}
cell.label.textColor = UIColor.black
} else {
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if collectionView == self.productMenuCollectionView {
let cell = self.productMenuCollectionView.cellForItem(at: indexPath) as! ProductMenuCollectionViewCell
cell.label.textColor = CustomColor.primary
} else {
}
}

You are observing this behaviour because the cells are reused, so one cell can be used for one index path, but when that index path scrolls out of view, and new index paths scrolls into view, the same cell object could be used for one of the new cells. Whenever you dequeue a cell, keep in mind that you might be reconfiguring old cells!
So what happens is, one of the old selected cells moves out of view, and gets reconfigured for use at a new index path. Your code presumably removes the selected color from that cell at that time, so when you scroll back up, the color is gone.
What you should do is, in ProductMenuCollectionViewCell, override isSelected:
override var isSelected: Bool {
didSet {
if isSelected {
self.label.textColor = CustomColor.primary
} else {
self.label.textColor = UIColor.black
}
}
}
And in cellForItemAtIndexPath:
if collectionView.indexPathsForSelectedItems?.contains(indexPath) ?? false {
cell.isSelected = true
} else {
cell.isSelected = false
}

Related

Collection view cell, didSelectItem delegate

i have a collectionView with images in cells and by tapping on the item i am deleting it
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
UIView.animate(withDuration: 0.5) {
if let cell = collectionView.cellForItem(at: indexPath) as? CollectionViewCell {
cell.image.center.x += 300 }
} completion: { Bool in
ImageModel.images.remove(at: indexPath.row)
collectionView.deleteItems(at: [indexPath])
}
}
I can delete 1,2,3 cells, but if i scroll it downside sometimes i see on cells that they already moved (like the method UIView.animate was used, but without completion of it) Why is it happens?

Showing/Hiding images in CollectionView Cell

I'm trying to show some images and hide others using ".isHidden" in my CollectionView. But when I scroll down or reload the collectionView they either get reordered incorrectly or hidden entirely.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ReadBookCell", for: indexPath) as! ReadBookCell
let item = readBookArray[indexPath.item]
for star in cell.starImgOutletCollection {
if star.tag <= item.starRating {
star.isHidden = false
} else {
star.isHidden = true
}
}
return cell
}
Edit: Here is my prepareForReuse
override func prepareForReuse() {
super.prepareForReuse()
for star in starImgOutletCollection {
star.isHidden = true
}
}
Assuming your "stars" are 5 image views in a stack view like this:
And, assuming your starRating will be between 0 and 5 (Zero being no rating yet)...
In your cell class, create a reference to the stack view - since your question mentions starImgOutletCollection I'm assuming you are using #IBOutlet (that is, not creating your views via code), so:
#IBOutlet var starsStackView: UIStackView!
Then, still in your cell class, add this func:
func updateStars(_ starRating: Int) {
for i in 0..<starsStackView.arrangedSubviews.count {
starsStackView.arrangedSubviews[i].isHidden = i >= starRating
}
}
Now, in cellForRowAt
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ReadBookCell", for: indexPath) as! ReadBookCell
let item = readBookArray[indexPath.item]
cell.updateStars(item.starRating)
// do the other stuff to set labels, images, etc
// in the cell
return cell
}
You no longer need the outlet collection for the "star" image views, and you no longer need to implement prepareForReuse().

change collection view cell background colour dynamically on button action not working

I try to change the background colour of specific cell on button action. Color of cell is changing but but I scrolling this collection view the color of cell misplace from there original position
when I scrolling this collection view which contain question number, color position in this control misplaced like in this image
How I can handle this problem that collection view cell color never change their position automatically.
This is my code on button click to change the color :
let indexs = IndexPath(row: currentQuestion, section: 0)
let celi = questionNumberCollection.cellForItem(at: indexs)
celi?.backgroundColor = .blue
Problems is
You are changing the background color of the cell but you're not maintaining the state of the cell anywhere in your model which is important in the case where the cell is reused while scrolling.
Solution:
A simple and standard solution might be maintaining a state variable in a model or as a separate array, and change the background color in the cellforRowatIndexPath method.
Example:
struct Question {
var desc:String
var mark:Int
var status:AnsweringStatus
}
enum AnsweringStatus {
case notAttented,correct,inCorrect
}
class ViewController:UIViewController,UICollectionViewDataSource {
var dataSource:[Question]!
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataSource.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ShowCell", for: indexPath) as! ShowCell
switch dataSource[indexPath.row].status {
case .notAttented:
cell.backgroundColor = .gray
case .correct:
cell.backgroundColor = .red
case .inCorrect:
cell.backgroundColor = .green
}
return cell
}
}
Have showcased only the parts necessary to solve the issue. So on click of the button just changing the state in the respective model object using the index path and reloading the collection view will do the job.
Provide more insights about the issue, if this doesn't work for you.
I think your problem was that CollectionViews and TableView reuse the cells.
In your CollectionViewCell class use this method to reset the reused Cell to default values or Colors.
#IBAction func onButtonTappet(sender: UIButton) {
let indexs = IndexPath(row: currentQuestion, section: 0)
let cell = questionNumberCollection.cellForItem(at: indexs) as? MyCell
cell?.onButtonTapped = true
}
class MyCell: UICollectionViewCell {
var onButtonTapped: Bool = false {
didSet { checkBackgroundColor() }
}
override func prepareForReuse() {
checkBackgroundColor()
}
override func awakeFromNib() {
checkBackgroundColor()
}
private func checkBackgroundColor() {
self.backgroundColor = onButtonTapped ? myTappedColor : myDefaultColor
}
}

Load more data when NSCollectionView is scrolled to end of content (macOS)

In my macOS application I have an NSCollectionView. I downloaded data and put that in the collection view, and now I want to load more data when the user scrolls to the bottom of the available content. On iOS, I could do something like this with UIScrollViewDelegate.scrollViewDidEndDragging(_:willDecelerate:), but I'm unsure how to do this on macOS.
How can I trigger a load when the user scrolls to the bottom of the view?
One possible approach that I have used on iOS is to trigger the load when a particular item is displayed. On macOS, this can be accomplished using the collectionView(_:willDisplay:forRepresentedObjectAt) method of NSCollectionViewDelegate. Something like this:
func collectionView(_ collectionView: NSCollectionView, willDisplay item: NSCollectionViewItem, forRepresentedObjectAt indexPath: IndexPath) {
if indexPath == self.lastIndexPath {
loadNextSection()
}
}
func loadNextSection() {
guard let self.loading == false else {
return
}
self.loading = true
self.loader.loadSection(nextSection) { objects in
self.nextSection += 1
self.sections.append(objects)
self.lastIndexPath = IndexPath(indexes: [self.sections.count, objects.count])
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
}
it looks, I solved it. Create an observation. And connect nscollectionview's scrollview and use this when I scrolling..:
if (self.scrollViewColl.verticalScroller?.floatValue.isEqual(to: 1.0))! && loading == false
Alternatively you can check if the indexPath.item is equal to the data source array count, if so call the method that loads data in data source array.
func collectionView(_ collectionView: NSCollectionView, willDisplay item: NSCollectionViewItem, forRepresentedObjectAt indexPath: IndexPath) {
if indexPath.item == self.data_source_array.count-1 {
self.method_to_load_more_data()
}
}
Don't forget to reloadData() collection view after adding more data.

Swift CollectionViewCells Issue

Sorry for the vague title but I wasn't completely sure was to call it. I have a list of collection view cells in a collectionview these cells just have a white background. I have a total of 20 cells and I want the first one to have a cyan background and the fourth to have a green background. My issue is that if the list is big enough and I scroll the colors seem to be random sometimes 4 green and 2 cyan at the top instead of just 1 cyan and 1 green. I think this is due to using index path.row in the func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell method and assigning colors based on indexpath.row. I think index path.row changes as I scroll so when I scroll to the bottom index path.row is the item at the top of the screen not the top of the list. I understand this is not the proper way to achieve this, is there anyway to get the first/last item from the list instead of the first/last currently on my screen? Is there a better way to go about this entirely?
Here is a quick example of what the issue looks like - https://gyazo.com/e66d450e9ac50b1c9acd521c959dd067
EDIT:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int` is return 20 and in `func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell` this is what I have - `let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Invite Cell", for: indexPath) as! InviteCell
print(indexPath.row)
if indexPath.row == 0 {
cell.InviteCellContainer.backgroundColor = UIColor.cyan
} else if indexPath.row == 5 {
cell.InviteCellContainer.backgroundColor = UIColor.green
}
return cell
}
Cells are reused. Make sure that any UI element has a defined state in cellForItemAt:
In your code the state is undefined if the row is not 0 and not 5. So you need to add a case for all other indexes:
if indexPath.row == 0 {
cell.InviteCellContainer.backgroundColor = UIColor.cyan
} else if indexPath.row == 5 {
cell.InviteCellContainer.backgroundColor = UIColor.green
} else {
cell.InviteCellContainer.backgroundColor = UIColor.gray // or what the default color is
}
return cell
A more descriptive syntax is a switch expression
switch indexPath.row {
case 0: cell.InviteCellContainer.backgroundColor = UIColor.cyan
case 4: cell.InviteCellContainer.backgroundColor = UIColor.green
default: cell.InviteCellContainer.backgroundColor = UIColor.gray
}
Assuming your code isn't faulty, which I can't tell because you didn't include any of it, it looks like you should be calling collectionView.reloadData() after each cellForItemAt. Let me know what happens when you do this.
You should set the background color regardless of its place
if(indexPath.row == 0) {
cell.InviteCellContainer.backgroundColor = UIColor.cyan
} else if(indexPath.row == 5) {
cell.InviteCellContainer.backgroundColor = UIColor.green
} else {
cell.InviteCellContainer.backgroundColor = UIColor.white
}
return cell
It might be because you haven't defined the cell in a separate class and used the function prepareForReuse() to set the background back to white. Cells are reused within a collectionView so sometimes if you set the data (and don't reset it) it will remain the same when the cell is used again.
Hope this helps!