I have UICollectionView in ViewController class and I also have a class for UICollectionViewCell.
I have some cells and I need to detect which cell was tapped. How can I detect that?
Use this delegate method
func collectionView(_ collectionView: UICollectionView,
didSelectItemAt indexPath: IndexPath) {
let tappedCell = collectionView.cellForItem(at:indexPath) as! CustomCellClass
print(tappedCell.tag)
}
//
collectionView.delegate = self
//
class CustomVC:UIViewController,UICollectionViewDelegate,UICollectionViewDataSource { --- }
In Swift 5 you can use the didSelectItemAt method:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print(indexPath) //will return [0, 0] for the first cell, [0, 1] for the second cell etc...
}
Related
I am using delegate to make a connection between a UICollectionViewCell and a UICollectionViewController. In this connection I want to say, if user clicks on a UIView its super class is changed, I already did it by using gesture.
The only problem is, I think I have to implement this delegation into the didSelectItemAt protocol of UIcollectionView', which I am not sure to how to do it.
For example, first I did it in the cellForItemAt, which was a mistake, buy I could implement easily.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "listCell", for: indexPath) as? ListCell
cell?.selectionDelegate = self // implement the delegate
}
but I don't know how to do the same thing in didSelectItemAt, because I think I should do it here, not in cellForItemAt
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
}
Thank you so much for your help in advance
Let's say you had your original selection delegate:
protocol SelectionDelegate {
func didSelect(_ cell: ListCell)
}
Then you can easily implement collectionView's didSelect by just calling your selection delegate:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let cell = collectionView.cellForItem(at: indexPath) as? ListCell else { return }
didSelect(cell)
}
I am using Swift4 and Xcode9.
I create my collectionView cells by using the delegate method below;
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "mediaItemCell", for: indexPath) as! MediaItemCollectionViewCell
cell.mediaItemCellImageView.image = mediaPlayer?.item(at: indexPath.row)?.image
return cell
}
Then, I want to replace the image of selected cell with a "stop" image. To achieve this, I have tried to use didSelectItemAt method;
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if (mediaPlayer?.isPlaying == true) {
// stop the player
mediaPlayer?.stopPlayingMedia()
} else {
// change the image and start playing
let cell = collectionView.cellForItem(at: indexPath) as! MediaItemCollectionViewCell // CRASH
cell.mediaItemCellImageView.image = UIImage(named: "stop.png")
...
...
}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
// restore the original image
let cell = collectionView.cellForItem(at: indexPath) as! MediaItemCollectionViewCell
cell.mediaItemCellImageView.image = mediaPlayer?.item(at: indexPath.row)?.image
// stop playing
mediaPlayer?.stopPlayingMedia()
}
Now, when I select the cell which is already selected (hence the original image has already been replaced with "stop.png") the code above works; i.e. the original image is restored, which is done by stopPlayingMedia() method.
When I select a cell different than the currently selected one, the App crashes. I tried to move the didSelectItemAt call out of the delegate method, but it did not work.
Surprisingly, the logical check if collectionView.cellForItem(at: indexPath) is MediaItemCollectionViewCell succeeds when I select the currently selected cell, and fails when I select another cell.
I want to change the image of the cell, hence need to cast the variable type but it fails. Why does to cast fail when I select a different cell than the currently selected one?
Thanks
Edit:
I use collectionView.reloadData() to restore all the images to originals (I mentioned this in the OP - when stopPlayingMedia() is invoked). It appears that, if I add a private var index:IndexItem? to my ViewController class and update its value within didSelectItemAt (to save the last selected cell) and use the code below, the code does not crash
extension ViewController:MediaPlayerControllerDelegate {
// this delegate method is invoked when stopPlayingMedia() is called
func mediaPlayerControllerDidFinishPlaying() {
// restore the UI
// collectionView.reloadData() -> this causes the crash, instead, I only restore one cell
let cell = collectionView.cellForItem(at: index!) as! MediaItemCollectionViewCell
cell.mediaItemCellImageView.image = mediaPlayer?.item(at: (index?.row)!)?.image
timeLabel.text = "00:00"
progressBar.progress = 0.0
}
}
I tried the following to create cell and changing the image inside cell on selection. Not crashing at all. Please check the implementation, It might help you.
import UIKit
class ViewController: UIViewController {
private var isPlayingModel = [false, false, false, false, false] // initially all are stop, not playing
#IBOutlet weak var collectionView: UICollectionView! {
didSet{
collectionView.delegate = self
collectionView.dataSource = self
}
}
}
extension ViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return isPlayingModel.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "mediaItemCell", for: indexPath) as! MediaItemCollectionViewCell
cell.imageVw.image = isPlayingModel[indexPath.row] ? #imageLiteral(resourceName: "start") : #imageLiteral(resourceName: "stop")
return cell
}
}
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
isPlayingModel[indexPath.row] = !isPlayingModel[indexPath.row]
let cell = collectionView.cellForItem(at: indexPath) as! MediaItemCollectionViewCell
cell.imageVw.image = isPlayingModel[indexPath.row] ? #imageLiteral(resourceName: "start") : #imageLiteral(resourceName: "stop")
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.size.width, height: 60)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 4
}
}
class MediaItemCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var imageVw: UIImageView!
}
Check the implementation here.
You are not passing indexPath correctly to load a cell, that's why it is crashing.Edit your didSelectItemAt: method as follows:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let selectedCell = IndexPath(item: indexPath.item, section: indexPath.section)
let cell = collectionView.cellForItem(at: selectedCell) as! ACollectionViewCell
cell.imgView.image = UIImage(named: "3.jpg")
}
Collection view indexPath has element item, and not row
Try this and see: (let me know what does it print)
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
// restore the original image
if let cell = collectionView.cellForItem(at: indexPath) as? MediaItemCollectionViewCell {
if let cellImage = mediaPlayer?.item(at: indexPath.item)?.image { // Collection view indexPath has element `item`, and not `row`
cell.mediaItemCellImageView.image = cellImage
} else {
print("cellImage not found at indexPath - \(indexPath)")
}
} else {
print("collection view cell `MediaItemCollectionViewCell` not found at indexPath -\(indexPath) ")
}
// stop playing
mediaPlayer?.stopPlayingMedia()
}
I have a app which has simple UICollectionView
I am just need when did select any cell from this collectionView app make a vibration
here's my Code
import AudioToolbox
ManualWaveCollectionView : UICollectionViewDataSource , UICollectionViewDelegate , UICollectionViewDelegateFlowLayout{
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = self.collectionView.dequeueReusableCell(withReuseIdentifier: "locationsCell", for: indexPath) as! LocationCollectionViewCell
let location = self.cellLocations[indexPath.row]
cell.locationName.text = location.location
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.cellLocations.count
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
AudioServicesPlayAlertSound(SystemSoundID(kSystemSoundID_Vibrate))
AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
}
}
there are nothing happen it's just keep print () when click in the collectionViewCell
iam using Iphone 5s
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
AudioServicesPlayAlertSound(SystemSoundID(kSystemSoundID_Vibrate))
}
I'm trying to use prefetching which was introduced in iOS 10. But unable to achieve it.
I do set the delegate and enable the prefetching. But when i set a breakpoint or console log at prefetch, it does not pass through the function ever.
I know on fast scroll, the prefetching is skipped. but even on slowest scroll, its never been called
var dataSource = [IndexPath: Any]()
override func viewDidLoad() {
if #available(iOS 10.0, *) {
mainCollectionView.isPrefetchingEnabled = true
mainCollectionView.prefetchDataSource = self
}
//Other stuffs
}
//This is not being called
func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
print("Prefectch")
for indexPath in indexPaths {
dataSource[indexPath] = fetchDataSource(indexPath)
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyCell", for: indexPath) as! MyCell
cell.dataSource = dataSource[indexPath] ?? fetchDataSource(indexPath)
return cell
}
Call
override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
self.collectionView(collectionView, prefetchItemsAt: [indexPath])
}
To trigger the prefetch, and remove:
collectionViewLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
If you have it.
This was by me the case.
I have a UICollectionView that is a calendar. It's a single scrollable row. Some of the dates are unselectable as there is no data for that date. Others ARE selectable as there IS data for that date.
When a date is selected the calendar underlines the selected date and scrolls it to the center of the UICollectionView.
When a date is tapped that doesn't have a date I have this function...
override func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
return //is there data for the date at this indexPath?
}
This stops those cells being selected... however, it still deselects the previously selected cell.
There is a shouldDeselect function but I can't use this as I don't know the index of the tapped cell. So I can't determine whether the item should be deselected.
Is there a better way of doing this?
It simple works for me when I reload the collectionview:
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
// return true whithout reloadData when necessarily
collectionView.reloadData()
return false
}
I recently had the same issue and I ended up with the following approach:
Whenever user selects a cell, remember the position in a member variable:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
myMemberVariable = indexPath.row
}
After that call myCollectionView.reloadData()
In the cellForRowAtIndexPath: apply appropriate configuration for the cell:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MyCell.identifier, for: indexPath) as! MyCell
if indexPath.row == myMemberVariable && canSelectCellAt(indexPath: indexPath) {
cell.backgroundColor = .green
} else {
cell.backgroundColor = .gray
}
return cell
}
Where canSelectCellAt(indexPath: indexPath) is a function, which returns true for selectable cells and false for non-selectable cells,
I just came across this question (kind of late?) and to solve this you just need to use
func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool {
Instead of shouldSelect
My solution:
collectionView.allowsMultipleSelection = true
and in didSelect, deselect all previously selected index paths:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.indexPathsForSelectedItems?.filter({ $0 != indexPath }).forEach { indexPath in
collectionView.deselectItem(at: indexPath, animated: false)
}
}
My solution is to enable multiple selection
collectionView.allowsMultipleSelection = true
and
func collectionView(_ collectionView: UICollectionView, shouldDeselectItemAt indexPath: IndexPath) -> Bool {
return false
}