Invisible cells of UICollectionView are not deselected - Swift - swift

I have in my swift app two collection views. One which is a functions categories and another one which is the functions. The first one works as a filter to the second one. If I select "Cat1" then only functions with tag "Cat1" are displayed. This works great.
The functions categories collectionview is horizontal and I need to scroll to see all the cells. My issue/problem is already mentioned in another topics but I can not find the right anwser or technique.
Issue: If I select a category, the cell's background changes, fine. If now I scroll completely to the end of the collectionview and select the last cell, this one change as selected the the first one (previously selected) is not deselected.. I know that is a problem with reused cell but no idea how to manage that. Below my code :
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// Working with functions categories collection view
if collectionView == self.functionsCategoriesCollectionView {
let cell = collectionView.cellForItem(at: indexPath) as! DevicePageFunctionsCategoriesCVCell
cell.isHidden = false
cell.cellView.isHidden = false
cell.isSelected = true
cell.cellView.clipsToBounds = true
cell.cellView.layer.cornerRadius = 25
cell.cellView.addGradiant(colors: [UIColor(red: 127.0/255.0, green: 127.0/255.0, blue: 127.0/255.0, alpha: 1.0).cgColor, UIColor(red: 47.0/255.0, green: 47.0/255.0, blue: 47.0/255.0, alpha: 1.0).cgColor], angle: 45)
if cellSelectionIndexPath == indexPath {
// it was already selected
cellSelectionIndexPath = nil
collectionView.deselectItem(at: indexPath, animated: true)
cell.cellView.addGradiant(colors: [UIColor.clear.cgColor, UIColor.clear.cgColor], angle: 0)
self.filtered = GlobalVariables.currentProduct.functions.filter { _ in
return true
}
self.functionsCollectionView.reloadData()
} else {
// wasn't yet selected, so let's remember it
cellSelectionIndexPath = indexPath
// Filter with seletec category name
let cellCategoryName = ICDatabase.objects(FunctionCategory.self)[indexPath.row]
self.filtered = GlobalVariables.currentProduct.functions.filter { function in
return function.functionCategory.contains(cellCategoryName)
}
self.functionsCollectionView.reloadData()
}
}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
if collectionView == self.functionsCategoriesCollectionView {
if let cellToDeselect = collectionView.cellForItem(at: self.cellSelectionIndexPath) as? DevicePageFunctionsCategoriesCVCell {
cellToDeselect.isSelected = false
collectionView.deselectItem(at: self.cellSelectionIndexPath, animated: true)
cellToDeselect.cellView.addGradiant(colors: [UIColor.clear.cgColor, UIColor.clear.cgColor], angle: 0)
self.cellSelectionIndexPath = nil
// Get all functions
self.filtered = GlobalVariables.currentProduct.functions.filter { _ in
return true
}
self.functionsCollectionView.reloadData()
}
}
}
Thanks for your help!

Try this -
var selectedIndexPath: IndexPath?
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
cell.layer.backgroundColor = UIColor.black
self.selectedIndexPath = indexPath
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
cell.layer.backgroundColor = UIColor.white
self.selectedIndexPath = nil
}

Collection cells reuse memory. So you have to manually manage data and UI. If your requirement is to select only one category cell in collection view at any time, keep one variable in collection view and update it in didSelect method.
var selectedIndexPath: IndexPath?
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedIndexPath = indexPath
collectionView.reloadData()
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = UICollectionCell()
....
cell.backgroundColor = UIColor.black
if indexPath == selectedIndexPath {
cell.backgroundColor = UIColor.red
}
}

Related

When I select items in a collection view, it selects other items

when I select one item in a collection view, another item further down gets selected. Can someone tell me how to fix this?
Here's my code:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? Category3Cell {
cell.contentView.backgroundColor = #colorLiteral(red: 1, green: 0.8674157229, blue: 0.4739984274, alpha: 1)
recommends.append(cell.categoryKey)
print("select \(recommends)")
cell.isSelected = true
}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? Category3Cell {
cell.contentView.backgroundColor = .clear
recommends = recommends.filter(){$0 != cell.categoryKey}
print("deselect \(recommends)")
cell.isSelected = false
}
}
Cells are being reused when you scroll.
You must therefore make sure to reset things like background color in your func cellForItem(at indexPath: IndexPath).

Improper width of cell in collectionview

[updated]
I have created collectionview containing cells with different captions and different widths. It works fine when I read collection on launch of application.
But when I add new cell during usage of the application it has standard, narrow, width.
When I again relaunch the application it will again have correct width.
After adding reloadData() it works fine for one cell. But when I have multiple cells they are drawn one on each other.
And here is the code:
override func viewDidLoad() {
super.viewDidLoad()
projectCollectionView.delegate = self
projectCollectionView.dataSource = self
projectCollectionView.register(UINib(nibName: "projectCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "projectCollectionViewCell")
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: projectCollectionViewCell.identifier, for: indexPath) as? projectCollectionViewCell
else {
return projectCollectionViewCell()
}
cell.projectButton.setTitle("a title", for: .normal)
projectCollection[indexPath.row].cellIndex = indexPath.row
cell.projectButton.sizeToFit()
cell.layer.bounds.size.width = cell.projectButton.layer.bounds.width
return cell
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
projectCollectionView.collectionViewLayout.invalidateLayout()
projectCollectionView.reloadData()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
var result: Int = 0
for i in 0...projects.count-1 {
if (projects[i].status>=2) {
result += 1
}
}
return result
}
When I remove the row: cell.projectButton.sizeToFit() it started to look like this:
try it :-
relod collectionview after adding element.
collectionview.reloaddata()
[edited]
add flowlayout to collectionview
add below code
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let label = UILabel(frame: CGRect.zero)
label.text = textArray[indexPath.item]
label.sizeToFit()
return CGSize(width: label.frame.width, height: 32)
}
for more visit https://stackoverflow.com/a/53284536/12451658

Clicking an image in collection view selects other images

I have the following code:
import UIKit
import Photos
import PhotosUI
private let reuseIdentifier = "Cell"
class CollectionVC: UICollectionViewController, UICollectionViewDelegateFlowLayout {
var imageArray = [UIImage]()
override func viewDidLoad() {
super.viewDidLoad()
grapPhotos()
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imageArray.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath as IndexPath)
let imageView = cell.viewWithTag(1) as! UIImageView
collectionView.allowsMultipleSelection = true
cell.layer.cornerRadius = 4
imageView.image = imageArray[indexPath.row]
return cell
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) {
cell.layer.borderColor = #colorLiteral(red: 0.1411764771, green: 0.3960784376, blue: 0.5647059083, alpha: 1)
cell.layer.borderWidth = 5
}
}
override func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) {
cell.layer.borderColor = #colorLiteral(red: 0.1411764771, green: 0.3960784376, blue: 0.5647059083, alpha: 1)
cell.layer.borderWidth = 0
}
}
func grapPhotos() {
let imgManager = PHImageManager.default()
let requestOptions = PHImageRequestOptions()
requestOptions.isSynchronous = true
requestOptions.deliveryMode = .highQualityFormat
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
fetchOptions.predicate = NSPredicate(format: "mediaType = %d || mediaType = %d", PHAssetMediaType.image.rawValue, PHAssetMediaType.video.rawValue)
if let fetchResult : PHFetchResult = PHAsset.fetchAssets(with: fetchOptions) {
if fetchResult.count > 0 {
for i in 0..<fetchResult.count {
imgManager.requestImage(for: fetchResult.object(at: i), targetSize: CGSize(width: 200, height: 200), contentMode: .aspectFill, options: requestOptions, resultHandler: {
image, error in
self.imageArray.append(image!)
})
}
}
else {
self.collectionView?.reloadData()
print("No Photos")
}
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = collectionView.frame.width / 3 - 6
return CGSize(width: width, height: width)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 6.0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 6.0
}
}
I apologize it's a lot but I really have no idea where I am going wrong. This is all my code for my CollectionViewController, and then in storyboard I have a collection VC with an image in the one cell with a tag as 1 and then the one cell with an identifier as "Cell". The problem I am having is when I run the code on my phone, selecting one image ends up selecting others. For example: I select the first image, then I scroll down and another image is already selected. I did a little test and the first 21 images are almost unique. But then it's as if the image almost have the same id. Like selecting the first image also selects the 22nd image. By the way I am building a custom image picker.
There's more than one issue here, but the main problem you're complaining of is because you're forgetting that cells are reused. You need to configure cells in cellForItem, not in didSelect and didDeselect.
When your didSelect turns around and talks directly to a physical cell, it's doing totally the wrong thing. Instead, didSelect and didDeselect should talk to your data model, setting some property in the model's objects that says, the objects at these index paths should be considered selected, and the objects at these other index paths should not. Then reload the data.
Then, when cellForItem comes along, it must configure its cell completely, either displaying its selected appearance or its unselected appearance, based on the data model and index path. That way, if a cell was in a previous selected row but is now reused in an unselected row, it has the desired unselected appearance.
In other words, only cellForItem should think in terms of cells. Everyone else should think solely in terms of index path (or even better, in terms of some unique identifier associated with the data, but that's another story).

UICollectionview - blink when move item

I want to reorder my cells in my UICollectionView. But when I drop my item, the "cellForItemAt" method is called and this will cause the cell to flash (See image below).
What should I do to avoid this behavior ?
Thank you in advance for your help.
class ViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
private let cellIdentifier = "cell"
private let cells = [""]
private var longPressGesture: UILongPressGestureRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(self.handleLongGesture(gesture:)))
collectionView.addGestureRecognizer(longPressGesture)
}
//Selectors
#objc func handleLongGesture(gesture: UILongPressGestureRecognizer) {
switch(gesture.state) {
case .began:
guard let selectedIndexPath = collectionView.indexPathForItem(at: gesture.location(in: collectionView)) else {
break
}
collectionView.beginInteractiveMovementForItem(at: selectedIndexPath)
case .changed:
collectionView.updateInteractiveMovementTargetPosition(gesture.location(in: gesture.view!))
case .ended:
collectionView.endInteractiveMovement()
default:
collectionView.cancelInteractiveMovement()
}
}
}
// MARK: - UICollectionViewDataSource
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath)
return cell
}
func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
return true
}
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
}
}
// MARK: - UICollectionViewDelegateFlowLayout
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 100, height: 100)
}
}
You need to call endInteractiveMovement in perfomBatchUpdates.
But whenever endInteractiveMovement triggered, cellForRow called. So cell will be refreshed and new cell will added(check with random color extension). To secure that, you need to save selectedCell in variable. And return that cell when endInteractiveMovement called.
Declare currentCell in ViewController
var isEnded: Bool = true
var currentCell: UICollectionViewCell? = nil
Store selected cell in variable when gesture began & call endInteractiveMovement in performBatchUpdates.
So, your handleLongGesture func look like below:
//Selectors
#objc func handleLongGesture(gesture: UILongPressGestureRecognizer) {
switch(gesture.state) {
case .began:
guard let selectedIndexPath = collectionView.indexPathForItem(at: gesture.location(in: collectionView)) else {
break
}
isEnded = false
//store selected cell in currentCell variable
currentCell = collectionView.cellForItem(at: selectedIndexPath)
collectionView.beginInteractiveMovementForItem(at: selectedIndexPath)
case .changed:
collectionView.updateInteractiveMovementTargetPosition(gesture.location(in: gesture.view!))
case .ended:
isEnded = true
collectionView.performBatchUpdates({
self.collectionView.endInteractiveMovement()
}) { (result) in
self.currentCell = nil
}
default:
isEnded = true
collectionView.cancelInteractiveMovement()
}
}
Also need to change cellForRow
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if currentCell != nil && isEnded {
return currentCell!
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath)
cell.backgroundColor = .random
return cell
}
}
TIP
Use random color extension for better testing
extension UIColor {
public class var random: UIColor {
return UIColor(red: CGFloat(drand48()), green: CGFloat(drand48()), blue: CGFloat(drand48()), alpha: 1.0)
}
}
EDIT
If you have multiple sections.
Lets take array of array
var data: [[String]] = [["1","2"],
["1","2","3","4","5","6","7"],
["1","2","3","4","5","6","7","8","9","10","11","12","13","14","15"]]
Then you need to maintain data when reordering
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
print("\(sourceIndexPath) -> \(destinationIndexPath)")
let movedItem = data[sourceIndexPath.section][sourceIndexPath.item]
data[sourceIndexPath.section].remove(at: sourceIndexPath.item)
data[destinationIndexPath.section].insert(movedItem, at: destinationIndexPath.item)
}
You can try to call
collectionView.reloadItems(at: [sourceIndexPath, destinationIndexPath])
right after all your updates (drag and drop animation) are done.
For example call it after performBatchUpdates. It will remove blinking.

SelectItem in UICollectionView on first Load

I have a UICollectionView and there user can select on cell to use the Function in it. So and i need it to set the first cell to selected like in the First Screenshot. But the Problem there is when the cell is selected and user want to select ah other cell the first cell don´t unselect. I have try it to reload the tableview in did select item but that don´t run or with this func in viewdidload LabelCollectionView.selectItem(at: IndexPath(row: 1, section: 0), animated: true, scrollPosition: .bottom)
Have anyone an idea what i can make there ? thanks for Help:)
First Cell Selected
Here you see the Problem in the screenshot
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "StoryLabelCell", for: indexPath) as! StoryLabelCell
let labels = Labels[indexPath.row]
cell.backgroundColor = UIColor.lightGray
cell.layer.cornerRadius = 10
cell.Title.text = labels
if indexPath.row == 0 {
cell.backgroundColor = darkgrau
cell.layer.borderColor = black.cgColor
cell.layer.borderWidth = 2.0
}
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! StoryLabelCell
if indexPath.row == indexPath.row {
cell.backgroundColor = darkgrau
cell.layer.borderColor = black.cgColor
cell.layer.borderWidth = 2.0
}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! StoryLabelCell
if indexPath.row == indexPath.row {
cell.backgroundColor = lightGray
cell.layer.borderWidth = 0
}
}
What are you trying to do with checking for indexPath.row == indexPath.row? Basically you can try with cell.selected = true when it is selected and set it to false when deselected. Try below code and let me know if it works.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "StoryLabelCell", for: indexPath) as! StoryLabelCell
let labels = Labels[indexPath.row]
cell.backgroundColor = UIColor.lightGray
cell.layer.cornerRadius = 10
cell.Title.text = labels
if indexPath.row == 0 {
cell.backgroundColor = darkgrau
cell.layer.borderColor = black.cgColor
cell.layer.borderWidth = 2.0
cell.selected = true
}
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! StoryLabelCell
cell.backgroundColor = darkgrau
cell.layer.borderColor = black.cgColor
cell.layer.borderWidth = 2.0
cell.selected = true
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! StoryLabelCell
cell.backgroundColor = lightGray
cell.layer.borderWidth = 0
cell.selected = false
}
u should use the Cells selected state instead of comparing two values that are the same.
In you custom cell you react on cell selections!
override func setSelected(_ selected: Bool, animated: Bool) {
// change your cell like you want
if selected {
// make me dark
} else {
// make me light
}
// not tested, but somehow call the super
super.setSelected(selected:selected, animated: animated)
}
you can delete your didSelect and didDeselct func.
In the function cellForItem ... you can check if a cell is selected, if not select the one with indexPath.row == 0
For checking a selected cell use
collectionView.indexPathForSelectedItems
this is list, so you can select more than one cell. I am not sure, but i think u can setup the collectionView with multiple selections ore only one selection.
I hope this helps you so far.