I created an infinite horizontal collectionView presenting the first 6 items of my database, something like carousel. So, when I am scrolling to the right side, I can see those 6 items with the order I got them in that way:
1-2-3-4-5-6-1-2-3-4-5-6-1-2... etc. (bold 1 represents the first cell of collection view)
What I need to do now is when the collection view is rendered and I can see the first item, if I scroll left I want to present the last item I got from database (the 6th item).
...1-2-3-4-5-6-1-2-3-4-5-6-1-2-3-4-5-6-1-2... etc. (again, bold 1 represents the first cell of collection view)
Do you know how I can implement this feature?
There are various different ways to accomplish this... which method to use would depend on a number of things related to your UI layout.
But, assuming we do want to use a horizontal-scrolling collection view, and, based on your comments you want paging with two cells per page...
Let's say we have a simple string array for our data:
let myData: [String] = [
"1", "2", "3", "4", "5", "6",
]
If we have a simple single-label collection view cell, our cellForItemAt
could look like this:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: SimpleCell.identifier, for: indexPath) as! SimpleCell
let thisData = myData[indexPath.item % myData.count]
cell.theLabel.text = thisData
return cell
}
That gives us the "carousel" effect.
To allow the user to scroll right-to-left immediately, we cannot start at item: 0
.
So, we can set the number of items to some absurdly large number that nobody will ever scroll to, such as:
lazy var totalCells: Int = myData.count * 100_000
Then (I'm assuming) in viewDidLayoutSubviews()
- where we know the width of the collection view - we're setting the flow layout's .itemSize
... and we can scroll the collection view to totalCells / 2
.
Might look something like this:
var cvWidth: CGFloat = -1.0
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print(#function)
if cvWidth != myCollectionView.frame.size.width {
cvWidth = myCollectionView.frame.size.width
if let fl = myCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
let h = fl.itemSize.height
let w = (cvWidth - fl.minimumInteritemSpacing) * 0.5
fl.itemSize = .init(width: w, height: h)
}
// start with collection view "scrolled half-way"
// through the absurdly large number of items
let startItem: Int = totalCells / 2
myCollectionView.scrollToItem(at: IndexPath(item: startItem, section: 0), at: .left, animated: false)
// enable paging here
myCollectionView.isPagingEnabled = true
}
}
Now when the user first sees the collection view, it will already be scrolled to item 300_000
, and we can scroll both left and right.
Here's a quick, complete example...
simple single-label cell
class SimpleCell: UICollectionViewCell {
static let identifier: String = "simpleCell"
public var theLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 14, weight: .bold)
label.backgroundColor = .systemBlue
label.textColor = .white
label.textAlignment = .center
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
theLabel.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(theLabel)
let g = contentView.layoutMarginsGuide
NSLayoutConstraint.activate([
theLabel.topAnchor.constraint(equalTo: g.topAnchor),
theLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor),
theLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor),
theLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor),
])
// so we can see the cell framing
contentView.backgroundColor = .yellow
contentView.layer.borderColor = UIColor.red.cgColor
contentView.layer.borderWidth = 1
}
}
example view controller with collection view
class SimpleExampleVC: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
var myCollectionView: UICollectionView!
let myData: [String] = [
"1", "2", "3", "4", "5", "6",
]
// some absurdly large number that nobody will ever scroll to
lazy var totalCells: Int = myData.count * 100_000
override func viewDidLoad() {
super.viewDidLoad()
let fl = UICollectionViewFlowLayout()
fl.scrollDirection = .horizontal
fl.minimumLineSpacing = 0.0
fl.minimumInteritemSpacing = 0.0
// .itemSize width will be changed in viewDidLayoutSubviews()
fl.itemSize = .init(width: 120.0, height: 60.0)
myCollectionView = UICollectionView(frame: .zero, collectionViewLayout: fl)
myCollectionView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(myCollectionView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
myCollectionView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
myCollectionView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
myCollectionView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
myCollectionView.heightAnchor.constraint(equalToConstant: 64.0),
])
myCollectionView.register(SimpleCell.self, forCellWithReuseIdentifier: SimpleCell.identifier)
myCollectionView.dataSource = self
myCollectionView.delegate = self
// paging should be disabled at this point
// we don't have to worry if we're creating the collection view
// via code, as we're doing here... but...
// if we're using Storyboard, we want to make sure
// paging is NOT set to true
myCollectionView.isPagingEnabled = false
// just so we can see the framing
myCollectionView.backgroundColor = .darkGray
}
var cvWidth: CGFloat = -1.0
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print(#function)
if cvWidth != myCollectionView.frame.size.width {
cvWidth = myCollectionView.frame.size.width
if let fl = myCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
let h = fl.itemSize.height
let w = cvWidth * 0.5
fl.itemSize = .init(width: w, height: h)
}
// start with collection view "scrolled half-way"
// through the absurdly large number of items
let startItem: Int = totalCells / 2
myCollectionView.scrollToItem(at: IndexPath(item: startItem, section: 0), at: .left, animated: false)
// enable paging here
myCollectionView.isPagingEnabled = true
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return totalCells
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: SimpleCell.identifier, for: indexPath) as! SimpleCell
let thisData = myData[indexPath.item % myData.count]
cell.theLabel.text = thisData
return cell
}
}