collectionView cell not working in tapGestureRecognizer function - swift

I want to change a cell label text when I click/tap on a collectionView cell.
I tried the following way, but this not working.
#objc func tap(_ sender: UITapGestureRecognizer) {
let location = sender.location(in: self.collectionView)
let indexPath = self.collectionView.indexPathForItem(at: location)
if let index = indexPath {
let subL = zoneDict?.sublevel[index.row]
if (subL?.sublevel.count)! > 0 {
DispatchQueue.main.async {
self.zoneDict = subL!
print("self.zoneDict --\(self.zoneDict!)")
let cell = self.collectionView.dequeueReusableCell(withReuseIdentifier: "colViewCell", for: index) as! CollectionViewCell
cell.zoneNameLabel.text = self.zoneDict?.name // Cannot update the text label. It show the default value
print("zone name-- \(self.zoneDict?.name)") // Its print the result.
}
self.delegate?.selectedZoneWithCellItems(items: "cell")
}
}
}

I think when you tap collectionViewCell then iOS system default call function didSelectItemAtIndexPath of CollectionView so that you must handle default event selected cell by the way register UITapGestureRecognizer for your cell and after that you must set property of view (isUserInteractionEnabled = true).
For example: self.yourview.isUserInteractionEnabled = true

you can use this in the VC containing the collection view
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = segmentCollectionView.dequeueReusableCell(withReuseIdentifier: SegmentCellId, for: indexPath) as! CollectionViewCell
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(printHello))
cell.addGestureRecognizer(tapGesture)
return cell
}
#objc func printHello(){
print("Hello")
}

You can use its delegate method in this way to change the cell label text when clicked on cell: -> Here I am using two arrays to update the text of the label as an example:
//Variables that are used below.
let nameCapitalArr1 = ["ABC", "DFG", "EFG", "HIJ", "KLM", "NOP", "QRS", "TUV", "WXY", "Z"]
let nameSmallArr2 = ["abc", "dfg", "efg", "hij", "klm", "nop", "qrs", "tuv", "wxy", "z"]
var changeFlag: Bool = false
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return nameCapitalArr1.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "LabelTextCollectionViewCell", for: indexPath) as? LabelTextCollectionViewCell else { return UICollectionViewCell() }
cell.nameTextLabel.text = !changeFlag ? nameCapitalArr1[indexPath.row] : nameSmallArr2[indexPath.row]
return cell
}
/*That method is called when tapping on the cell and reload that particular cell and also change the label text.*/
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
changeFlag = !changeFlag
collectionView.reloadItems(at: [indexPath])
}
Output: -> Its reflect the cell text with capital to small array values while tapping on the cell with toggle effect.

Related

Hero ViewController animation with collectionView - swift

I'm trying to present a view controller whenever I tap on a collection view cellwith the zoom effect.
As far as I know Hero framework works using HeroID's, you set the same id in the fromView and in the toView and the frameworks does the hard work for you.
I have set It up this way:
in viewcontroller1 :
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! CollectionViewCell
cell.heroID = "profheroid"
let viewcontroller2 = ViewController2()
viewcontroller2.configureController(with: arrayOfModels[indexPath.item])
viewcontroller2.heroModalAnimationType = .zoom
viewcontroller2.modalPresentationStyle = .fullScreen
self.navigationController?.present(viewcontroller2, animated: true, completion: nil)
}
and in viewcontroller2:
override func viewDidLoad() {
super.viewDidLoad()
view.heroID = "profheroid"
}
The problem happens whenever I tap on a collectionviewCell the presentation happens correctly but I see so many cells being presented at the same time.
I think this happens because cell.heroID = "profheroid" is applied to more cells at the same time.
How can I make sure that when the cell is presented the heroID's are only in the cell tapped and in the viewcontroller's view??
I think you must clear this heroID when cell is reused and after VC is presented.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
cell.heroID = nil // or empty
}
I think you must reset heroID to nil in cellForItemAt:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: YourCollectionViewCell.description(), for: indexPath) as? YourCollectionViewCell else {
return UICollectionViewCell()
}
cell.heroID = nil
return cell
}
Reset all visible cell's heroID to nil, and only set heroID for cell selected with value before present:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.visibleCells.forEach { cell in
cell.heroID = nil //reset heroID
}
guard let cell = collectionView.cellForItem(at: indexPath) else { return }
cell.heroID = "profheroid"
let viewcontroller2 = ViewController2()
viewcontroller2.configureController(with: arrayOfModels[indexPath.item])
viewcontroller2.heroModalAnimationType = .zoom
viewcontroller2.modalPresentationStyle = .fullScreen
self.navigationController?.present(viewcontroller2, animated: true, completion: nil)
}

I open a second view that displays a series of images in a collection, Can I press on an image of the collection for use image in the first view?

I open a second view that displays a series of images in a collection, Can I press on an image of the collection for use image in the first view?
//from first ViewController
#IBAction func backgroundButton(_ sender: UIButton) {
}
//SecondViewController
class SecondViewController: UIViewController {
#IBAction func returnHome(_ sender: UIButton) {
self.dismiss(animated: true, completion: nil)
}
......
extension SecondViewController: UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width/2.5, height: collectionView.frame.width/2)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CustomCell
cell.data = self.data[indexPath.item]
return cell
}
}
class CustomCell: UICollectionViewCell {
var data: CustomData? {
didSet {
guard let data = data else { return }
bg.image = data.backgroundImage
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(imageTapped(tapGestureRecognizer:)))
bg.isUserInteractionEnabled = true
bg.addGestureRecognizer(tapGestureRecognizer)
}
}
#objc func imageTapped(tapGestureRecognizer: UITapGestureRecognizer) {
let tappedImage = tapGestureRecognizer.view as! UIImageView
self.addSubview(tappedImage)
}
// When I tap on Image in this case addSubview but I want use that image in First View Controller

Invisible cells of UICollectionView are not deselected - 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
}
}

ReloadData is not working for a UICollectionView

I would like to change my entier array used to created all my cells with a button and then reload my collection view, but it is not working. I've tried to reload the collection view into the main thread but it also not working
I've tried to reload the collection view into the main thread, or just using collectionView.reloadData() but it also not working
#IBAction func BicepsBtn(_ sender: Any) {
self.current = "Biceps"
Data = user.getMuscleDataChart(typeMuscle: current)
listMuscle = user.getListMuscle(typeMuscle: current)
collectionView.reloadData()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return listMuscle.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
let switchView = cell.viewWithTag(1000) as? UISwitch
let titleView = cell.viewWithTag(1001) as? UILabel
if let titleView = titleView, let switchView = switchView {
titleView.text = self.listMuscle[indexPath.row].label
titleView.textColor = self.listMuscle[indexPath.row].color
switchView.tag = indexPath.row
switchView.addTarget(self, action: #selector(yourFunc),
for: UIControl.Event.valueChanged)
}
return cell
}
Here is my current code, when the user is going to click on the Biceps button, the function will fetch all data about this muscle and then trying to reload.
Any idea ?
Thanks in advance

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.