How to Track UICollectionView index - ios

I want a variable in my code that keeps track of the index of my UICollectionView, but I can't get it to work. After some troubleshooting, I've boiled down the code to the following, which if pasted into an empty viewController should work since no storyboard is involved. The animated gif illustrates the problem. Initially my variable "selectedItem" is equal to the UICollectionView Cell text which reflects the data = [0,1,2,3], but then when I swipe right, it immediately becomes off by 1. Then it stays off by 1 until at the last cell where it matches again. The pattern repeats when going in reverse. Thanks for any help --
import UIKit
class CodeCollView2: UIViewController, UICollectionViewDataSource,UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
var data = [0,1,2,3] //["0", "1", "2", "3" ]
let cellId = "cellId2"
var selectedItem = 0
lazy var cView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.isPagingEnabled = true
cv.dataSource = self
cv.delegate = self
return cv
}()
var indexLabel: UILabel = {
let label = UILabel()
label.text = ""
label.font = UIFont.systemFont(ofSize: 30)
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
}
func setupViews() {
cView.register(CCell2.self, forCellWithReuseIdentifier: cellId)
view.addSubview(cView)
cView.translatesAutoresizingMaskIntoConstraints = false
cView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
cView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
cView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
cView.heightAnchor.constraint(equalToConstant: 200).isActive = true
view.addSubview(indexLabel)
indexLabel.translatesAutoresizingMaskIntoConstraints = false
indexLabel.bottomAnchor.constraint(equalTo: cView.topAnchor).isActive = true
indexLabel.centerXAnchor.constraint(equalTo: cView.centerXAnchor).isActive = true
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! CCell2
selectedItem = indexPath.item
indexLabel.text = "seletedItem = \(selectedItem)"
cell.itemValue = data[selectedItem]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return collectionView.frame.size
}
}
//============== CVCell ==================
class CCell2: UICollectionViewCell {
var itemValue: Int? {
didSet {
if let val = itemValue {
itemLabel.text = "\(val)"
}
}
}
var itemLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 100)
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .lightGray
addSubview(itemLabel)
itemLabel.translatesAutoresizingMaskIntoConstraints = false
itemLabel.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
itemLabel.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

As Nikita's answer mentions, cellForItemAt is called when a cell is going to be shown, even if you only see a bit of it and go back to the previous one, so you shouldn't use to decided what cell is at the centre.
scrollViewDidScroll is the right way of tracking which cell you have at the centre, and you can print what index you are on with something like this:
func scrollViewDidScroll(_ scrollView:UIScrollView)
{
let midX:CGFloat = scrollView.bounds.midX
let midY:CGFloat = scrollView.bounds.midY
let point:CGPoint = CGPoint(x:midX, y:midY)
guard
let indexPath:IndexPath = collectionView.indexPathForItem(at:point)
else
{
return
}
let currentPage:Int = indexPath.item
indexLabel.text = "seletedItem = \(currentPage)"
}

Tracking the selected item in the 'cellForItemAt' is not a good idea. I would suggest you to track it in the scrollViewDidScroll delegate method of the UIScrollViewDelegate.
Something like this should work:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let currentPage = cView.contentOffset.x / self.view.bounds.width
}

Related

UICollectionViewCell is not being correctly called from the UIViewController

I am trying to create a custom overlay for a UICollectionViewCell that when a user selects an image it puts a gray overlay with a number (ie. order) that the user selected the image in. When I run my code I do not get any errors but it also appears to do nothing. I added some print statements to help debug and when I run the code I get "Count :0" printed 15 times. That is the number of images I have in the library. When I select the first image in the first row I still get "Count: 0" as I would expect, but when I select the next image I get the print out that you see below. It appears that the count is not working but I am not sure why. What am I doing wrong? I can't figure out why the count is wrong, but my primary issue/concern I want to resolve is why the overlay wont display properly?
Print Statement
Cell selected: [0, 0]
Count :0
Count :0
Count :0
Cell selected: [0, 4]
Count :0
View Controller
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? TestCVCell {
cell.setupView()
print("Cell selected: \(indexPath)")
}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? TestCVCell {
cell.backgroundColor = nil
cell.imageView.alpha = 1
}
}
Custom Overlay
lazy var circleView: UIView = {
let view = UIView()
view.backgroundColor = .black
view.layer.cornerRadius = self.countSize.width / 2
view.alpha = 0.4
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
lazy var countLabel: UILabel = {
let label = UILabel()
let font = UIFont.preferredFont(forTextStyle: .headline)
label.font = UIFont.systemFont(ofSize: font.pointSize, weight: UIFont.Weight.bold)
label.textAlignment = .center
label.textColor = .white
label.adjustsFontSizeToFitWidth = true
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
private func setup(){addSubview(circleView)
addSubview(circleView)
addSubview(countLabel)
NSLayoutConstraint.activate([
circleView.leadingAnchor.constraint(equalTo: leadingAnchor),
circleView.trailingAnchor.constraint(equalTo: trailingAnchor),
circleView.topAnchor.constraint(equalTo: topAnchor),
circleView.bottomAnchor.constraint(equalTo: bottomAnchor),
countLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
countLabel.trailingAnchor.constraint(equalTo: trailingAnchor),
countLabel.topAnchor.constraint(equalTo: topAnchor),
countLabel.bottomAnchor.constraint(equalTo: bottomAnchor),
])
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
TestCVCell: UICollectionViewCell
override var isSelected: Bool {
didSet { overlay.isHidden = !isSelected }
}
var imageView: UIImageView = {
let view = UIImageView()
view.clipsToBounds = true
view.contentMode = .scaleAspectFill
view.backgroundColor = UIColor.gray
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
var count: Int = 0 {
didSet { overlay.countLabel.text = "\(count)" }
}
let overlay: CustomAssetCellOverlay = {
let view = CustomAssetCellOverlay()
view.isHidden = true
return view
}()
func setupView() {
addSubview(imageView)
addSubview(overlay)
print("Count :\(count)")
NSLayoutConstraint.activate([
overlay.topAnchor.constraint(equalTo: imageView.topAnchor),
overlay.bottomAnchor.constraint(equalTo: imageView.bottomAnchor),
overlay.leftAnchor.constraint(equalTo: imageView.leftAnchor),
overlay.rightAnchor.constraint(equalTo: imageView.rightAnchor),
])
}
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
override func layoutSubviews() {
super.layoutSubviews()
imageView.frame = self.bounds
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
fatalError("init(coder:) has not been implemented")
}
Based on your other question, I'm guessing you are trying to do something like this...
Display images from device Photos, and allow multiple selections in order:
and, when you de-select a cell - for example, de-selecting my 2nd selection - you want to re-number the remaining selections:
To accomplish this, you need to keep track of the cell selections in an array - as they are made - so you can maintain the numbering.
Couple ways to approach this... here is one.
First, I'd suggest re-naming your count property to index, and, when setting the value, show or hide the overlay:
var index: Int = 0 {
didSet {
overlay.countLabel.text = "\(index)"
// hide if count is Zero, show if not
overlay.isHidden = index == 0
}
}
When you dequeue a cell from cellForItemAt, see if the indexPath is in our "tracking" array and set the cell's .index property appropriately (which will also show/hide the overlay).
Next, when you select a cell:
add the indexPath to our tracking array
we can set the .index property - with the count of our tracking array - directly to update the cell's appearance, because it won't affect any other cells
When you de-select a cell, we have to do additional work:
remove the indexPath from our tracking array
reload the cells so they are re-numbered
Here is a complete example - lots of comments in the code.
CircleView
class CircleView: UIView {
// simple view subclass that keeps itself "round"
// (assuming it has a 1:1 ratio)
override func layoutSubviews() {
layer.cornerRadius = bounds.width * 0.5
}
}
CustomAssetCellOverlay
class CustomAssetCellOverlay: UIView {
lazy var circleView: CircleView = {
let view = CircleView()
view.backgroundColor = UIColor(red: 0.0, green: 0.5, blue: 1.0, alpha: 1.0)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
lazy var countLabel: UILabel = {
let label = UILabel()
let font = UIFont.preferredFont(forTextStyle: .headline)
label.font = UIFont.systemFont(ofSize: font.pointSize, weight: UIFont.Weight.bold)
label.textAlignment = .center
label.textColor = .white
label.adjustsFontSizeToFitWidth = true
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
private func setup(){addSubview(circleView)
addSubview(circleView)
addSubview(countLabel)
NSLayoutConstraint.activate([
// circle view at top-left
circleView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 4.0),
circleView.topAnchor.constraint(equalTo: topAnchor, constant: 4.0),
// circle view Width: 28 Height: 1:1 ratio
circleView.widthAnchor.constraint(equalToConstant: 28.0),
circleView.heightAnchor.constraint(equalTo: circleView.widthAnchor),
// count label constrained ot circle view
countLabel.leadingAnchor.constraint(equalTo: circleView.leadingAnchor),
countLabel.trailingAnchor.constraint(equalTo: circleView.trailingAnchor),
countLabel.topAnchor.constraint(equalTo: circleView.topAnchor),
countLabel.bottomAnchor.constraint(equalTo: circleView.bottomAnchor),
])
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
}
TestCVCell
class TestCVCell: UICollectionViewCell {
var imageView = UIImageView()
var index: Int = 0 {
didSet {
overlay.countLabel.text = "\(index)"
// hide if count is Zero, show if not
overlay.isHidden = index == 0
}
}
let overlay: CustomAssetCellOverlay = {
let view = CustomAssetCellOverlay()
view.backgroundColor = UIColor.black.withAlphaComponent(0.4)
view.isHidden = true
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
contentView.addSubview(imageView)
contentView.addSubview(overlay)
imageView.translatesAutoresizingMaskIntoConstraints = false
overlay.translatesAutoresizingMaskIntoConstraints = false
// constrain both image view and overlay to full contentView
NSLayoutConstraint.activate([
imageView.topAnchor.constraint(equalTo: contentView.topAnchor),
imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
overlay.topAnchor.constraint(equalTo: imageView.topAnchor),
overlay.bottomAnchor.constraint(equalTo: imageView.bottomAnchor),
overlay.leadingAnchor.constraint(equalTo: imageView.leadingAnchor),
overlay.trailingAnchor.constraint(equalTo: imageView.trailingAnchor),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
TrackSelectionsViewController
class TrackSelectionsViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UINavigationControllerDelegate {
var myCollectionView: UICollectionView!
// array to track selected cells in the order they are selected
var selectedCells: [IndexPath] = []
// to load assests when needed
let imgManager = PHImageManager.default()
let requestOptions = PHImageRequestOptions()
// will be used to get photos data
var fetchResult: PHFetchResult<PHAsset>!
override func viewDidLoad() {
super.viewDidLoad()
// set main view background color to a nice medium blue
view.backgroundColor = UIColor(red: 0.25, green: 0.5, blue: 1.0, alpha: 1.0)
// request Options to be used in cellForItemAt
requestOptions.isSynchronous = false
requestOptions.deliveryMode = .opportunistic
// vertical stack view for the full screen (safe area)
let mainStack = UIStackView()
mainStack.axis = .vertical
mainStack.spacing = 0
mainStack.translatesAutoresizingMaskIntoConstraints = false
// add it to the view
view.addSubview(mainStack)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
mainStack.topAnchor.constraint(equalTo: g.topAnchor, constant:0.0),
mainStack.leadingAnchor.constraint(equalTo: g.leadingAnchor),
mainStack.trailingAnchor.constraint(equalTo: g.trailingAnchor),
mainStack.bottomAnchor.constraint(equalTo: g.bottomAnchor),
])
// create a label
let label = UILabel()
// add the label to the main stack view
mainStack.addArrangedSubview(label)
// label properties
label.textColor = .white
label.textAlignment = .center
label.text = "Select Photos"
label.heightAnchor.constraint(equalToConstant: 48.0).isActive = true
// setup the collection view
setupCollection()
// add it to the main stack view
mainStack.addArrangedSubview(myCollectionView)
// start the async call to get the assets
grabPhotos()
}
func setupCollection() {
let layout = UICollectionViewFlowLayout()
myCollectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
myCollectionView.delegate = self
myCollectionView.dataSource = self
myCollectionView.backgroundColor = UIColor.white
myCollectionView.allowsMultipleSelection = true
myCollectionView.register(TestCVCell.self, forCellWithReuseIdentifier: "cvCell")
}
//MARK: CollectionView
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? TestCVCell {
// add newly selected cell (index path) to our tracking array
selectedCells.append(indexPath)
// when selecting a cell,
// we can update the appearance of the newly selected cell
// directly, because it won't affect any other cells
cell.index = selectedCells.count
}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
// when de-selecting a cell,
// we can't update the appearance of the cell directly
// because if it's not the last cell selected, the other
// selected cells need to be re-numbered
// get the index of the deselected cell from our tracking array
guard let idx = selectedCells.firstIndex(of: indexPath) else { return }
// remove from our tracking array
selectedCells.remove(at: idx)
// reloadData() clears the collection view's selected cells, so
// get a copy of currently selected cells
let curSelected: [IndexPath] = collectionView.indexPathsForSelectedItems ?? []
// reload collection view
// we do this to update all cells' appearance,
// including re-numbering the currently selected cells
collectionView.reloadData()
// save current Y scroll offset
let saveY = collectionView.contentOffset.y
collectionView.performBatchUpdates({
// re-select previously selected cells
curSelected.forEach { pth in
collectionView.selectItem(at: pth, animated: false, scrollPosition: .centeredVertically)
}
}, completion: { _ in
// reset Y offset
collectionView.contentOffset.y = saveY
})
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
guard fetchResult != nil else { return 0 }
return fetchResult.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cvCell", for: indexPath) as! TestCVCell
imgManager.requestImage(for: fetchResult.object(at: indexPath.item) as PHAsset, targetSize: CGSize(width:120, height: 120),contentMode: .aspectFill, options: requestOptions, resultHandler: { (image, error) in
cell.imageView.image = image
})
// get the index of this indexPath from our tracking array
// if it's not there (nil), set it to -1
let idx = selectedCells.firstIndex(of: indexPath) ?? -1
// set .count property to index + 1 (arrays are zero-based)
cell.index = idx + 1
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = collectionView.frame.width
return CGSize(width: width/4 - 1, height: width/4 - 1)
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
myCollectionView.collectionViewLayout.invalidateLayout()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 1.0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 1.0
}
//MARK: grab photos
func grabPhotos(){
DispatchQueue.global(qos: .background).async {
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key:"creationDate", ascending: false)]
self.fetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions)
if self.fetchResult.count == 0 {
print("No photos found.")
}
DispatchQueue.main.async {
self.myCollectionView.reloadData()
}
}
}
}
Note: This is example code only!!! It should not be considered "production ready."
Shouldn't your var count: Int = 0 be set at your CollectionView delegate?
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? TestCVCell {
cell.setupView()
cell.count = indexPath.item
print("Cell selected: \(indexPath)")
}
}

Can I add an action to a customCell in a UICollectionView?

First I had a couple of UIButtons with a #IBAction to play a sound when tapped. Because I had some problems with constraints on different iPhones I decided to go for a UICollectionView with CustomCells. Now I got this working I want to setup the CustomCells to play a sound when tapped. So I think I will have to setup something like an 'Action' on the custom cells for playing a sound. I do have a 'sort of action' that prints out that the user tapped a custom cell like;
print("tapped tag > ", self.tag)
The original #IBAction I used on a UIButton was:
#IBAction func sound2(_ sender: UIButton) {
sound2.play()
Again, I 'think' I need to setup a sort of action to the CustomCell for playing a sound?
And I 'think' I need to declare somewhere what sound to play along with the self.tag?
So each customCell will play a different sound..
The only problem, (rookieAlert) I don't know where to start :(
But I want to learn!
Can somebody point me into the right direction or provide me with some information?
Here is the full code of the project to make things more clear:
import UIKit
import AVFoundation
class ViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
var countries = [UIImage(named:"c1"),UIImage(named:"c2"),UIImage(named:"c3"),UIImage(named:"c4"),UIImage(named:"c5"),UIImage(named:"c6"),UIImage(named:"c7"),UIImage(named:"c8"),UIImage(named:"c9"),UIImage(named:"c10")]
#IBOutlet weak var soundsCollectionView: UICollectionView!
lazy var cv: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
layout.itemSize = CGSize(width: 100, height: 100)
var cc = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
cc.translatesAutoresizingMaskIntoConstraints = false
cc.register(CustomCell.self, forCellWithReuseIdentifier: "CustomCell")
cc.delegate = self
cc.dataSource = self
cc.backgroundColor = .black
return cc
}()
let sounds = ["sound1", "sound2", "sound3", "sound4", "sound5"]
var sound1 = AVAudioPlayer()
var sound2 = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
view.addSubview(cv)
cv.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
cv.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
cv.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
cv.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
// Setting up sound1
do {
sound1 = try AVAudioPlayer(contentsOf: URL.init(fileURLWithPath: Bundle.main.path(forResource: "sound1", ofType: "mp3")!))
sound1.prepareToPlay()
} catch {
print(error)
}
// Setting up sound2
do {
sound2 = try AVAudioPlayer(contentsOf: URL.init(fileURLWithPath: Bundle.main.path(forResource: "sound2", ofType: "mp3")!))
sound2.prepareToPlay()
} catch {
print(error)
}
}
#IBAction func sound2(_ sender: UIButton) {
if sound2.isPlaying {
sound2.stop()
sound1.stop()
sender.setImage(UIImage(named: "sound1_off.jpg"), for: .normal)
} else {
sound2.play()
sender.setImage(UIImage(named: "sound1_on.jpg"), for: .normal)
}
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.centerImageView.image = countries[indexPath.row]
cell.tag = indexPath.row
cell.backgroundColor = UIColor.green
return cell
}
//define constant value as per your requirement and declare this variables globally
let inset: CGFloat = 10 //define as per your requirement
let minimumInteritemSpacing: CGFloat = 10 //define as per your requirement
let cellsPerRow = 3
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let marginsAndInsets = inset * 2 + collectionView.safeAreaInsets.left + collectionView.safeAreaInsets.right + minimumInteritemSpacing * CGFloat(cellsPerRow - 1)
let itemWidth = ((collectionView.bounds.size.width - marginsAndInsets) / CGFloat(cellsPerRow)).rounded(.down)
return .init(width: itemWidth, height: itemWidth)
}
}
class CustomCell: UICollectionViewCell {
lazy var centerImageView: UIImageView = {
var img = UIImageView()
img.translatesAutoresizingMaskIntoConstraints = false
img.clipsToBounds = true
img.isUserInteractionEnabled = true
img.contentMode = .scaleAspectFill
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handler(_:)))
tapGesture.numberOfTapsRequired = 1
img.addGestureRecognizer(tapGesture)
return img
}()
#objc private func handler(_ sender: UITapGestureRecognizer) {
print("tapped tag > ", self.tag)
}
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(centerImageView)
centerImageView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
centerImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
centerImageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
centerImageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
This is for same sound in all cell: try to delete gesture from your centerImage in custom cell, after that add the gesture in cellForItemAt and call the function like this:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.centerImageView.image = countries[indexPath.row]
cell.backgroundColor = UIColor.green
let gesture = UITapGestureRecognizer(target: self, action: #selector(playCellSound))
cell.centerImageView.addGestureRecognizer(gesture)
return cell
}
after that write the func below cellForItemAt:
#objc func playCellSound() {
sound2.play()
print("play sound...")
}
FOR DIFFERENT SOUND IN CELLS:
the number of sounds must be equal the number of countries:
let sounds = ["sound1", "sound2", "sound3", "sound4", "sound5", "sound6", "sound7", "sound8", "sound9", "sound10"]
after that declare your sounds like this:
var sound1 = AVAudioPlayer()
var sound2 = AVAudioPlayer()
var sound3 = AVAudioPlayer()
var sound4 = AVAudioPlayer()
var sound5 = AVAudioPlayer()
var sound6 = AVAudioPlayer()
var sound7 = AVAudioPlayer()
var sound8 = AVAudioPlayer()
var sound9 = AVAudioPlayer()
var sound10 = AVAudioPlayer()
set the other sound like sound 1 and sound 2
now this is your cellForItemAt:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.centerImageView.image = countries[indexPath.row]
cell.backgroundColor = UIColor.green
return cell
}
set numberOfItemsInSection like this:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return countries.count // dynamic number of items based on your countries array
}
implement the method didiSelectItemAt
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let s = sounds[indexPath.item]
switch s {
case "sound1":
sound1.play()
print("s1")
case "sound2":
sound2.play()
print("s2")
case "sound3":
sound3.play()
print("s3")
case "sound4":
sound4.play()
print("s4")
case "sound5":
sound5.play()
print("s5")
case "sound6":
sound6.play()
print("s6")
case "sound7":
sound7.play()
print("s7")
case "sound8":
sound8.play()
print("s8")
case "sound9":
sound9.play()
print("s9")
case "sound10":
sound10.play()
print("s10")
default:
break
}
}
in your custom cell delete tap gesture for centerImageView:
class CustomCell: UICollectionViewCell {
lazy var centerImageView: UIImageView = {
var img = UIImageView()
img.contentMode = .scaleAspectFill
img.clipsToBounds = true
img.isUserInteractionEnabled = true
img.translatesAutoresizingMaskIntoConstraints = false
return img
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(centerImageView)
centerImageView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
centerImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
centerImageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
centerImageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
FOR SYSTEM SOUNDS:
replace your var declaration like this:
var sound1: SystemSoundID = 1016
var sound2: SystemSoundID = 1322
var sound3: SystemSoundID = 1110
var sound4: SystemSoundID = 1070
var sound5: SystemSoundID = 1109
var sound6: SystemSoundID = 1117
var sound7: SystemSoundID = 1106
var sound8: SystemSoundID = 1118
var sound9: SystemSoundID = 1256
var sound10: SystemSoundID = 1116
deleting setting up sound 1, 2 etc. and replace the switch case in didSelectItemAt with this:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let s = sounds[indexPath.item]
switch s {
case "sound1":
AudioServicesPlaySystemSound (sound1)
print("s1")
case "sound2":
AudioServicesPlaySystemSound(sound2)
print("s2")
case "sound3":
AudioServicesPlaySystemSound(sound3)
print("s3")
case "sound4":
AudioServicesPlaySystemSound(sound4)
print("s4")
case "sound5":
AudioServicesPlaySystemSound(sound5)
print("s5")
case "sound6":
AudioServicesPlaySystemSound(sound6)
print("s6")
case "sound7":
AudioServicesPlaySystemSound(sound7)
print("s7")
case "sound8":
AudioServicesPlaySystemSound(sound8)
print("s8")
case "sound9":
AudioServicesPlaySystemSound(sound9)
print("s9")
case "sound10":
AudioServicesPlaySystemSound(sound10)
print("s10")
default:
break
}
}
}
this is not the best way but it work fine, it will make you understand how it works and help you to learn
I believe what you are looking for is implementation of the didSelectItemAt function. You can implement like so by putting with your other collection view functions like cellForItemAt. Then, make sure the required sound is closely linked with your data source. Meaning you can access the sound you want via indexPath.item, which is a way of identifying the specific cell that got tapped.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// Use indexPath.item to determine what sound to play
}

Making animating horizontal indicator using collectionView

I'm working on App where I have to manage my horizontal indicator on CollectionviewCell, while scrolling CollectionView and by selecting any Cell.
This is what I’m looking for(Just focus on Horizontal CollectionView)
As I have implemented this But I’m not getting the exact functionality/behavior AND unable to stick horizontal indicator on collectionviewCell while scrolling. I can only stick if i make a horizontal indicator in CollectionViewCell But in this Case I’m unable to apply sliding animation.
This is what I have implemented
Here is my Code Snippet for MENUBAR
import UIKit
class MenuBar: UITableViewHeaderFooterView, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UIScrollViewDelegate {
//MARK:- Properties
let cellId = "cellId"
let menuNames = ["Recommeded", "Popular", "Free", "Trending", "Love Songs", " Free Songs"]
var horizontalBarLeftAnchorConstraint : NSLayoutConstraint?
lazy var collectionView : UICollectionView = {
let cv = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
cv.translatesAutoresizingMaskIntoConstraints = false
cv.dataSource = self
cv.delegate = self
cv.showsHorizontalScrollIndicator = false
cv.backgroundColor = UIColor.clear
return cv
}()
let horizontalView : UIView = {
let v = UIView()
v.backgroundColor = UIColor.red
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
//MARK:- default methods
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
setupCollectionView()
setupHorizontalBar()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//MARK:- Functions
private func setupCollectionView() {
if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.scrollDirection = .horizontal
}
addSubview(collectionView)
collectionView.register(MenuCell.self, forCellWithReuseIdentifier: cellId)
collectionView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
collectionView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
collectionView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
collectionView.topAnchor.constraint(equalTo: topAnchor).isActive = true
}
private func setupHorizontalBar() {
addSubview(horizontalView)
let textSize = (menuNames[0] as NSString).size(withAttributes: nil)
let cellSize = textSize.width + 50
let indicatorLineWith = 25/2
let x = (cellSize/2) - CGFloat(indicatorLineWith)
//x,y,w,h
horizontalBarLeftAnchorConstraint =
horizontalView.leftAnchor.constraint(equalTo: leftAnchor, constant: x )
horizontalBarLeftAnchorConstraint?.isActive = true
horizontalView.heightAnchor.constraint(equalToConstant: 5).isActive = true
horizontalView.widthAnchor.constraint(equalToConstant: 25).isActive = true
horizontalView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
}
//MARK:- CollectionView methods
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return menuNames.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! MenuCell
cell.menuName = menuNames[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let size = (menuNames[indexPath.row] as NSString).size(withAttributes: nil)
return CGSize(width: (size.width + 50), height: frame.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
//getting Cell size on screen
let attributes : UICollectionViewLayoutAttributes = collectionView.layoutAttributesForItem(at: indexPath)!
let indicatorSize = 25/2
let cellRect = attributes.frame
let cellFrameInSuperView = collectionView.convert(cellRect, to: collectionView)
let textSize = (menuNames[indexPath.row] as NSString).size(withAttributes: nil)
let cellSize = textSize.width + 50
let x = (CGFloat(cellFrameInSuperView.origin.x) + (cellSize/2)) - CGFloat(indicatorSize)
horizontalBarLeftAnchorConstraint?.constant = x
UIView.animate(withDuration: 0.75, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
self.layoutIfNeeded()
}, completion: nil)
}
}
Here is my code snippet for MENUCELL:-
import UIKit
//MARK:- CollectionViewBaseCell
class CollectionViewBaseCell : UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {}
}
//MARK:- MenuCell
class MenuCell : CollectionViewBaseCell {
//MARK:- Properties
var menuName : String? {
didSet {
label.text = menuName
}
}
let label : UILabel = {
let lbl = UILabel()
lbl.translatesAutoresizingMaskIntoConstraints = false
lbl.text = "Label"
return lbl
}()
//MARK:- default methods
override func setupViews() {
addSubview(label)
//x,y,w,h Constraint
label.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
}
}
TRY THIS:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == self.collectionView{
print( scrollView.contentOffset.x ) // use this contentOffset
}
}

Issues with CollectionViewControllerCell not Showing up CollectionViewController

I'm making an iPhone app using Firebase and I'm having a small issue with the following code.
import UIKit
import Firebase
class ChatController: UICollectionViewController, UITextFieldDelegate, UICollectionViewDelegateFlowLayout {
var game: Game? {
didSet {
navigationItem.title = game?.name
}
}
lazy var inputTextField: UITextField = {
let textField = UITextField()
textField.placeholder = "Enter message..."
textField.translatesAutoresizingMaskIntoConstraints = false
return textField
}()
let cellId = "cellId"
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.alwaysBounceVertical = true
collectionView?.backgroundColor = UIColor.white
collectionView?.register(ChatMessageCell.self, forCellWithReuseIdentifier: cellId)
setupInputComponents()
}
var messages = [Message]()
func observeMessages() {
let ref = Database.database().reference().child("games").child((game?.id)!).child("messages")
ref.observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let message = Message()
message.setValuesForKeys(dictionary)
self.messages.append(message)
DispatchQueue.main.async {
self.collectionView?.reloadData()
}
}
}, withCancel: nil)
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return messages.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath as IndexPath) as! ChatMessageCell
// let message = messages[indexPath.item]
// cell.textView.text = message.text
cell.backgroundColor = UIColor.blue
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: NSIndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: 80)
}
// Sets up the entire message sending
// container for the send button and input
// text field
func setupInputComponents() {
// Creates the container for send button
// and input text field
let containerView = UIView()
containerView.backgroundColor = UIColor.white
containerView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(containerView)
// anchor constraints and x,y,w,h
// for positioning the container
containerView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
containerView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
containerView.heightAnchor.constraint(equalToConstant: 50).isActive = true
// Creates the send button
let sendButton = UIButton(type: .system)
sendButton.setTitle("Send", for: .normal)
sendButton.translatesAutoresizingMaskIntoConstraints = false
sendButton.addTarget(self, action: #selector(handleSend), for: .touchUpInside)
containerView.addSubview(sendButton)
// Positions the send button within the container
sendButton.rightAnchor.constraint(equalTo: containerView.rightAnchor).isActive = true
sendButton.centerYAnchor.constraint(equalTo: containerView.centerYAnchor).isActive = true
sendButton.widthAnchor.constraint(equalToConstant: 80).isActive = true
sendButton.heightAnchor.constraint(equalTo: containerView.heightAnchor).isActive = true
// Creates the input text field
containerView.addSubview(inputTextField)
// Positions the send button within the container
inputTextField.leftAnchor.constraint(equalTo: containerView.leftAnchor, constant: 8).isActive = true
inputTextField.centerYAnchor.constraint(equalTo: containerView.centerYAnchor).isActive = true
inputTextField.rightAnchor.constraint(equalTo: sendButton.leftAnchor).isActive = true
inputTextField.heightAnchor.constraint(equalTo: containerView.heightAnchor).isActive = true
// Create a seperator for this component
let seperatorLineView = UIView()
seperatorLineView.backgroundColor = UIColor(r: 220, g: 220, b: 220)
seperatorLineView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(seperatorLineView)
// Positions the seperator
seperatorLineView.leftAnchor.constraint(equalTo: containerView.leftAnchor, constant: 8).isActive = true
seperatorLineView.widthAnchor.constraint(equalTo: containerView.widthAnchor).isActive = true
seperatorLineView.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
seperatorLineView.heightAnchor.constraint(equalToConstant: 1).isActive = true
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
#objc func handleSend() {
let ref = Database.database().reference().child("games").child("-L6lIJt_cw6saNNWMgu1").child("messages")
ref.keepSynced(true)
let childRef = ref.childByAutoId()
let values = ["text": inputTextField.text!]
childRef.updateChildValues(values)
}
}
I have some commented out pieces which are actually what I'm wanting to display as text in my cells but I can't even get the colored boxes to show up for me inside the CollectionViewController. The DB retrieval code works perfectly fine and I can display what it gives me in a TableViewController. It's just not in the CollectionViewController that I've made here.
What is happening exactly is that the application builds and loads successfully, but when I navigate to this screen, the cells that I am creating aren't showing up. Those lines specifically are the following:
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return messages.count
// return 5
}
override func collectionView(_ collectionView: UICollectionView,, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath as IndexPath) as! ChatMessageCell
// let message = messages[indexPath.item]
// cell.textView.text = message.text
cell.backgroundColor = UIColor.blue
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: NSIndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: 80)
}
You need to add the following delegates and data source.
To your class add UICollectionViewDataSource and UICollectionViewDelegate.
Then in your viewDidLoad add self.collectionView?.delegate = selfand self.collectionView?.dataSource = self.
This tells the view controller that it’s delegate and data source methods are in its own class.
This should solve your issue. Sorry if the formatting is off a bit in this answer. On my phone so can’t see the question as I type the answer!

Invalid Selector Using Delegate Pattern

I am attempting to use the delegate pattern to animate a change in height for a collectionView. The button that triggers this change is in the header. However when I press the button not only does the height not change but it also crashes with the error
'NSInvalidArgumentException', reason: '-[UIButton length]: unrecognized selector sent to instance 0x12f345b50'
I feel like I have done everything right but it always crashes when I click the button. Does anyone see anything wrong and is there anyway that I can animate the change in height for the cell the way I want it to. This is the cell class along with the protocol and the delegate.
import Foundation
import UIKit
protocol ExpandedCellDelegate:NSObjectProtocol{
func viewEventsButtonTapped(indexPath:IndexPath)
}
class EventCollectionCell:UICollectionViewCell {
var headerID = "headerID"
weak var delegateExpand:ExpandedCellDelegate?
public var indexPath:IndexPath!
var eventArray = [EventDetails](){
didSet{
self.eventCollectionView.reloadData()
}
}
var enentDetails:Friend?{
didSet{
var name = "N/A"
var total = 0
seperator.isHidden = true
if let value = enentDetails?.friendName{
name = value
}
if let value = enentDetails?.events{
total = value.count
self.eventArray = value
seperator.isHidden = false
}
if let value = enentDetails?.imageUrl{
profileImageView.loadImage(urlString: value)
}else{
profileImageView.image = imageLiteral(resourceName: "Tokyo")
}
self.eventCollectionView.reloadData()
setLabel(name: name, totalEvents: total)
}
}
let container:UIView={
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.cornerRadius = 16
view.layer.borderColor = UIColor.lightGray.cgColor
view.layer.borderWidth = 0.3
return view
}()
//profile image view for the user
var profileImageView:CustomImageView={
let iv = CustomImageView()
iv.layer.masksToBounds = true
iv.layer.borderColor = UIColor.lightGray.cgColor
iv.layer.borderWidth = 0.3
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
//will show the name of the user as well as the total number of events he is attending
let labelNameAndTotalEvents:UILabel={
let label = UILabel()
label.textColor = .black
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
return label
}()
let seperator:UIView={
let view = UIView()
view.backgroundColor = .lightGray
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
//collectionview that contains all of the events a specific user will be attensing
lazy var eventCollectionView:UICollectionView={
let flow = UICollectionViewFlowLayout()
flow.scrollDirection = .vertical
let spacingbw:CGFloat = 5
flow.minimumLineSpacing = 0
flow.minimumInteritemSpacing = 0
let cv = UICollectionView(frame: .zero, collectionViewLayout: flow)
//will register the eventdetailcell
cv.translatesAutoresizingMaskIntoConstraints = false
cv.backgroundColor = .white
cv.register(EventDetailsCell.self, forCellWithReuseIdentifier: "eventDetails")
cv.register(FriendsEventsViewHeader.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: headerID)
cv.delegate = self
cv.dataSource = self
cv.backgroundColor = .blue
cv.contentInset = UIEdgeInsetsMake(spacingbw, 0, spacingbw, 0)
cv.showsVerticalScrollIndicator = false
cv.bounces = false
return cv
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.setUpCell()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setLabel(name:String,totalEvents:Int){
let mainString = NSMutableAttributedString()
let attString = NSAttributedString(string:name+"\n" , attributes: [NSAttributedStringKey.foregroundColor:UIColor.black,NSAttributedStringKey.font:UIFont.systemFont(ofSize: 14)])
mainString.append(attString)
let attString2 = NSAttributedString(string:totalEvents == 0 ? "No events" : "\(totalEvents) \(totalEvents == 1 ? "Event" : "Events")" , attributes: [NSAttributedStringKey.foregroundColor:UIColor.darkGray,NSAttributedStringKey.font:UIFont.italicSystemFont(ofSize: 12)])
mainString.append(attString2)
labelNameAndTotalEvents.attributedText = mainString
}
}
//extension that handles creation of the events detail cells as well as the eventcollectionview
//notice the delegate methods
//- Mark EventCollectionView DataSource
extension EventCollectionCell:UICollectionViewDataSource{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return eventArray.count
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerID, for: indexPath) as! FriendsEventsViewHeader
header.viewEventsButton.addTarget(self, action: #selector(viewEventsButtonTapped), for: .touchUpInside)
return header
}
#objc func viewEventsButtonTapped(indexPath:IndexPath){
print("View events button touched")
if let delegate = self.delegateExpand{
delegate.viewEventsButtonTapped(indexPath: indexPath)
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier:"eventDetails" , for: indexPath) as! EventDetailsCell
cell.details = eventArray[indexPath.item]
cell.backgroundColor = .yellow
cell.seperator1.isHidden = indexPath.item == eventArray.count-1
return cell
}
}
//- Mark EventCollectionView Delegate
extension EventCollectionCell:UICollectionViewDelegateFlowLayout{
//size for each indvidual cell
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: 50)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: collectionView.frame.width, height: 40)
}
}
This is the view that ultimately is supposed to be handling the expansion via the delegate function.
import UIKit
import Firebase
class FriendsEventsView: UIViewController,UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout {
var friends = [Friend]()
var followingUsers = [String]()
var notExpandedHeight : CGFloat = 100
var expandedHeight : CGFloat?
var isExpanded = [Bool]()
//so this is the main collectonview that encompasses the entire view
//this entire view has eventcollectionCell's in it which in itself contain a collectionview which also contains cells
//so I ultimately want to shrink the eventCollectionView
lazy var mainCollectionView:UICollectionView={
// the flow layout which is needed when you create any collection view
let flow = UICollectionViewFlowLayout()
//setting the scroll direction
flow.scrollDirection = .vertical
//setting space between elements
let spacingbw:CGFloat = 5
flow.minimumLineSpacing = spacingbw
flow.minimumInteritemSpacing = 0
//actually creating collectionview
let cv = UICollectionView(frame: .zero, collectionViewLayout: flow)
//register a cell for that collectionview
cv.register(EventCollectionCell.self, forCellWithReuseIdentifier: "events")
cv.translatesAutoresizingMaskIntoConstraints = false
//changing background color
cv.backgroundColor = .red
//sets the delegate of the collectionView to self. By doing this all messages in regards to the collectionView will be sent to the collectionView or you.
//"Delegates send messages"
cv.delegate = self
//sets the datsource of the collectionView to you so you can control where the data gets pulled from
cv.dataSource = self
//sets positon of collectionview in regards to the regular view
cv.contentInset = UIEdgeInsetsMake(spacingbw, 0, spacingbw, 0)
return cv
}()
//label that will be displayed if there are no events
let labelNotEvents:UILabel={
let label = UILabel()
label.textColor = .lightGray
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.font = UIFont.italicSystemFont(ofSize: 14)
label.text = "No events found"
label.isHidden = true
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
//will set up all the views in the screen
self.setUpViews()
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: imageLiteral(resourceName: "close_black").withRenderingMode(.alwaysOriginal), style: .done, target: self, action: #selector(self.goBack))
}
func setUpViews(){
//well set the navbar title to Friends Events
self.title = "Friends Events"
view.backgroundColor = .white
//adds the main collection view to the view and adds proper constraints for positioning
view.addSubview(mainCollectionView)
mainCollectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
mainCollectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
mainCollectionView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0).isActive = true
mainCollectionView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
//adds the label to alert someone that there are no events to the collectionview and adds proper constrains for positioning
mainCollectionView.addSubview(labelNotEvents)
labelNotEvents.centerYAnchor.constraint(equalTo: mainCollectionView.centerYAnchor, constant: 0).isActive = true
labelNotEvents.centerXAnchor.constraint(equalTo: mainCollectionView.centerXAnchor, constant: 0).isActive = true
//will fetch events from server
self.fetchEventsFromServer()
}
// MARK: CollectionView Datasource for maincollection view
//will let us know how many eventCollectionCells tht contain collectionViews will be displayed
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print(friends.count)
isExpanded = Array(repeating: false, count: friends.count)
return friends.count
}
//will control the size of the eventCollectionCells that contain collectionViews
height is decided for the collectionVIew here
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let event = friends[indexPath.item]
if let count = event.events?.count,count != 0{
notExpandedHeight += (CGFloat(count*40)+10)
}
self.expandedHeight = notExpandedHeight
if isExpanded[indexPath.row] == true{
return CGSize(width: collectionView.frame.width, height: expandedHeight!)
}else{
return CGSize(width: collectionView.frame.width, height: 100)
}
}
//will do the job of effieicently creating cells for the eventcollectioncell that contain eventCollectionViews using the dequeReusableCells function
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "events", for: indexPath) as! EventCollectionCell
cell.backgroundColor = UIColor.orange
cell.indexPath = indexPath
cell.delegateExpand = self
cell.enentDetails = friends[indexPath.item]
return cell
}
}
extension FriendsEventsView:ExpandedCellDelegate{
func viewEventsButtonTapped(indexPath:IndexPath) {
isExpanded[indexPath.row] = !isExpanded[indexPath.row]
print(indexPath)
UIView.animate(withDuration: 0.8, delay: 0.0, usingSpringWithDamping: 0.9, initialSpringVelocity: 0.9, options: UIViewAnimationOptions.curveEaseInOut, animations: {
self.mainCollectionView.reloadItems(at: [indexPath])
}, completion: { success in
print("success")
})
}
}
I used this post for reference to implement
Expandable UICollectionViewCell
This is a very common mistake.
The passed parameter in a target / action selector is always the affected UI element which triggers the action in your case the button.
You cannot pass an arbitrary object for example an indexPath because there is no parameter in the addTarget method to specify that arbitrary object.
You have to declare the selector
#objc func viewEventsButtonTapped(_ sender: UIButton) {
or without a parameter
#objc func viewEventsButtonTapped() {
UIControl provides a third syntax
#objc func viewEventsButtonTapped(_ sender: UIButton, withEvent event: UIEvent?) {
Any other syntax is not supported.

Resources