UICollectionView cell loads one image on top of another image - ios

I had everything working perfectly in test.
In production, a user saved a few images, two are ok but for some reason, two are doubling up on top of another image.
When tapping on the image (didSelectItemAt)
collectionView.reloadData()
Gets called and each tap, changes the image to clear it up into just one image.
I've worked back from this point but I'm stuck.
Images loaded in viewDidLoad
db.collection("SAVED IMAGE IDS").getDocuments()
{
(querySnapshot, err) in
if let err = err
{
print("Error getting documents: \(err)")
}
else
{
for document in querySnapshot!.documents
{
let id = document.documentID
let Ref = Storage.storage().reference(forURL: "SavedUserImages/\(id)")
Ref.getData(maxSize: 1 * 1024 * 1024)
{
data, error in
if error != nil
{
print("Error: Image could not download!")
}
else
{
let image = UIImage(data: data!)
self.picArray.append(image!)
self.imageID.append(id)
self.collectionView.reloadData()
}
}
}
}
}
Image loads in cell for row
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID, for: indexPath as IndexPath)
let data = picArray[indexPath.row]
cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = UIScreen.main.scale
let iv = UIImageView()
cell.contentView.addSubview(iv)
iv.frame = cell.contentView.frame
iv.image = data
collectionView.selectItem(at: IndexPath(item: 0, section: 0) as IndexPath, animated: false, scrollPosition: .init())
return cell
}
Thanks in advance for any help

With this code:
you are adding another image view every time you reload the cell.
Instead, you need to design your cell to already have the image view and change your code to:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID, for: indexPath as IndexPath)
let data = picArray[indexPath.row]
cell.iv.image = data
return cell
}
Edit - further explanation and examples...
Based on the code you've shown, you are using a default UICollectionViewCell instead of a custom subclassed cell.
So, if we do a complete example, using SF Symbol images from 0 to 14 for the picArray, using your approach:
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
let cellID: String = "cell"
var collectionView: UICollectionView!
var cvWidth: CGFloat = 0
// let's use an array of images for this example
var picArray: [UIImage] = []
override func viewDidLoad() {
super.viewDidLoad()
// create images 0 through 14
for i in 0..<15 {
if let img = UIImage(systemName: "\(i).circle") {
picArray.append(img)
}
}
let fl = UICollectionViewFlowLayout()
fl.scrollDirection = .vertical
collectionView = UICollectionView(frame: .zero, collectionViewLayout: fl)
collectionView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(collectionView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: g.topAnchor),
collectionView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
collectionView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
])
collectionView.dataSource = self
collectionView.delegate = self
// it appears you're using a default collection view cell class
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellID)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// only do this is the collection view frame has changed
if cvWidth != collectionView.frame.width {
cvWidth = collectionView.frame.width
if let fl = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
fl.itemSize = CGSize(width: cvWidth, height: 200.0)
}
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return picArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID, for: indexPath as IndexPath)
let data = picArray[indexPath.row]
cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = UIScreen.main.scale
// this is wrong... we're Adding ANOTHER image view every time
let iv = UIImageView()
cell.contentView.addSubview(iv)
iv.frame = cell.contentView.frame
iv.image = data
// this makes no sense, but I'll leave it here
collectionView.selectItem(at: IndexPath(item: 0, section: 0) as IndexPath, animated: false, scrollPosition: .init())
return cell
}
}
It looks like this at the start:
if we scroll all the way down - to where the "14" image should be the bottom cell, it looks like this:
If we scroll back to the top:
and after scrolling up and down several times:
As we can see, as the cells are reused we keep adding more and more image views on top of each other.
So, instead, let's create a simple custom cell subclass that creates and adds an image view when it is created:
class SimpleImageCell: UICollectionViewCell {
let imgView = UIImageView()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
imgView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(imgView)
let g = contentView.layoutMarginsGuide
NSLayoutConstraint.activate([
imgView.topAnchor.constraint(equalTo: g.topAnchor),
imgView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
imgView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
imgView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
])
}
}
and we'll use an almost identical view controller - the only differences are registering our SimpleImageCell class, and using a correct cellForItemAt func:
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
let cellID: String = "cell"
var collectionView: UICollectionView!
var cvWidth: CGFloat = 0
// let's use an array of images for this example
var picArray: [UIImage] = []
override func viewDidLoad() {
super.viewDidLoad()
// create images 0 through 14
for i in 0..<15 {
if let img = UIImage(systemName: "\(i).circle") {
picArray.append(img)
}
}
let fl = UICollectionViewFlowLayout()
fl.scrollDirection = .vertical
collectionView = UICollectionView(frame: .zero, collectionViewLayout: fl)
collectionView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(collectionView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: g.topAnchor),
collectionView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
collectionView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
])
collectionView.dataSource = self
collectionView.delegate = self
// register cell class that already has an image view
collectionView.register(SimpleImageCell.self, forCellWithReuseIdentifier: cellID)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// only do this is the collection view frame has changed
if cvWidth != collectionView.frame.width {
cvWidth = collectionView.frame.width
if let fl = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
fl.itemSize = CGSize(width: cvWidth, height: 200.0)
}
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return picArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID, for: indexPath) as! SimpleImageCell
let data = picArray[indexPath.item]
cell.imgView.image = data
return cell
}
}
The results:
We can scroll up and down all we want, and we will never have images "on top of" each other.

Related

UICollectionView doesn't configure nor prefetch the second item until scrolling

I use UICollectionView to present cells. Each cell takes up the full screen size. The collection view is created with several items while only the first one is configured and displayed on the screen.
The problem is that the second item is not configured nor prefetched unless collectionView is scrolled down.
In my use case cell configuration fetches data from remote server, which I prefer doing the sooner the better. When I scroll down but the second cell isn't configured there is nothing to present yet.
I suspected that the layout has something to do with it, so I tried to use UICollectionViewFlowLayout as well as UICollectionViewCompositionalLayout and in both cases the problem occurs.
Is it possible to force the collection view to call the configure method of the second cell earlier?
I created a demo swift project with collectionView presenting screen sized colored rectangles. Cell configuration and prefetch is logged to the console with their indexPath.
import Foundation
import UIKit
enum Section {
case main
}
typealias DataSource = UICollectionViewDiffableDataSource<Section, UIColor>
typealias Snapshot = NSDiffableDataSourceSnapshot<Section, UIColor>
class CollectionViewController: UIViewController, UICollectionViewDelegateFlowLayout,
UICollectionViewDataSourcePrefetching {
private lazy var dataSource: DataSource = makeDataSource()
private let collectionView: UICollectionView
private static let cellIdentifier = "CollectionViewCell"
init() {
// let layout = Self.makeFlowLayout()
let layout = Self.makeCompositionLayout()
print("Log: collectionview layout - \(layout.description)")
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.register(
UICollectionViewCell.self,
forCellWithReuseIdentifier: Self.cellIdentifier
)
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
setupCollectionView()
applySnapshot(animatingDifferences: false)
}
private static func makeFlowLayout() -> UICollectionViewFlowLayout {
let flowLayout = UICollectionViewFlowLayout()
flowLayout.scrollDirection = .vertical
flowLayout.minimumLineSpacing = 0
return flowLayout
}
private static func makeCompositionLayout() -> UICollectionViewLayout {
let config = UICollectionViewCompositionalLayoutConfiguration()
config.interSectionSpacing = 0
let fullSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1),
heightDimension: .fractionalHeight(1)
)
let item = NSCollectionLayoutItem(layoutSize: fullSize)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: fullSize, subitems: [item])
let layout = NSCollectionLayoutSection(group: group)
return UICollectionViewCompositionalLayout(section: layout, configuration: config)
}
private func setupCollectionView() {
collectionView.backgroundColor = .white
collectionView.delegate = self
collectionView.prefetchDataSource = self
collectionView.isPrefetchingEnabled = true
collectionView.contentInsetAdjustmentBehavior = .never
view.addSubview(collectionView)
collectionView.translatesAutoresizingMaskIntoConstraints = false
let contraints = [
collectionView.leftAnchor.constraint(equalTo: view.leftAnchor),
collectionView.rightAnchor.constraint(equalTo: view.rightAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
collectionView.topAnchor.constraint(equalTo: view.topAnchor)
]
NSLayoutConstraint.activate(contraints)
}
private func makeDataSource() -> DataSource {
let dataSource = DataSource(
collectionView: collectionView,
cellProvider: { (collectionView, indexPath, itemIdentifier) -> UICollectionViewCell? in
let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: Self.cellIdentifier,
for: indexPath
)
print("Log: configure cell in indexPath - \(indexPath)")
cell.backgroundColor = itemIdentifier
return cell
})
return dataSource
}
private func applySnapshot(animatingDifferences: Bool = true) {
let items = (0...200).map { _ in
UIColor(
red: CGFloat.random(in: 0...1),
green: CGFloat.random(in: 0...1),
blue: CGFloat.random(in: 0...1),
alpha: 1
)
}
var snapshot = Snapshot()
snapshot.appendSections([.main])
snapshot.appendItems(items)
dataSource.apply(snapshot, animatingDifferences: animatingDifferences)
}
// MARK: - UICollectionViewDelegateFlowLayout
func collectionView(
_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath
) -> CGSize {
return CGSize(width: collectionView.bounds.width, height: collectionView.bounds.height)
}
// MARK: - UICollectionViewDataSourcePrefetching
func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
print("Log: prefetch cells in indexPaths - \(indexPaths)")
}
}

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)")
}
}

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.

How to Track UICollectionView index

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
}

Resources