Infinite UICollectionview with UIPageControl - swift

In below given code I can simply infinitely scroll through the cells. The page control also seems to be working at least while scrolling forward (to the right of screen). But frankly, when I scroll to the left, I see weird index path jump of 3 items (by the way my data source has 3 items for test purposes). In the screen I notice nothing wrong. But page control seems to be freezing for a moment and starts to work again but with a shift and shows wrong dot for the cell. Any Ideas? Thanks for any help...
import UIKit
class HomeHeaderCell: CategoryCell {
private let cellId = "cellId"
private var timer: Timer?
private let infiniteSize = 1000
private var onlyOnce = true
// ...
let pageControl: UIPageControl = {
let rect = CGRect(origin: .zero, size: CGSize(width: 200, height: 50))
let pc = UIPageControl(frame: rect)
pc.currentPage = 0
pc.numberOfPages = 3
pc.pageIndicatorTintColor = .gray
pc.currentPageIndicatorTintColor = .red
pc.isUserInteractionEnabled = false
return pc
}()
override func setupViews() {
addSubview(baseCollectionView)
addSubview(pageControl)
baseCollectionView.delegate = self
baseCollectionView.dataSource = self
baseCollectionView.register(HeaderCell.self, forCellWithReuseIdentifier: cellId)
baseCollectionView.backgroundColor = .white
baseCollectionView.isPagingEnabled = true
baseCollectionView.isScrollEnabled = true
baseCollectionView.showsHorizontalScrollIndicator = false
baseCollectionView.anchor(top: topAnchor, leading: leadingAnchor, bottom: bottomAnchor, trailing: trailingAnchor)
pageControl.anchor(top: nil, leading: leadingAnchor, bottom: bottomAnchor, trailing: nil, padding: UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 0))
}
#objc func autoScroll() {
guard let currentItemNumber = baseCollectionView.indexPathsForVisibleItems.first?.item else { return }
let nextItemNumber = currentItemNumber + 1
let nextIndexPath = IndexPath(item: nextItemNumber, section: 0)
baseCollectionView.scrollToItem(at: nextIndexPath, at: .left, animated: true)
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
stopTimer()
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
startTimer()
}
func startTimer() {
timer = Timer.scheduledTimer(timeInterval: 4.0, target: self, selector: #selector(autoScroll), userInfo: nil, repeats: true)
}
func stopTimer() {
timer?.invalidate()
timer = nil
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return infiniteSize
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! HeaderCell
// Maybe can be done more elegantly...
if let foods = foodCategory?.foods {
let numberOfFood = indexPath.item % foods.count
cell.food = foods[numberOfFood]
pageControl.currentPage = numberOfFood
print(numberOfFood)
}
return cell
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
if onlyOnce {
let middleIndex = IndexPath(item: Int (infiniteSize / 2), section: 0)
baseCollectionView.scrollToItem(at: middleIndex, at: .centeredHorizontally, animated: false)
startTimer()
onlyOnce = false
}
}
override func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: frame.width, height: frame.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
private class HeaderCell: ItemCell {
override func setupViews() {
addSubview(imageView)
imageView.layer.cornerRadius = 0
imageView.layer.borderColor = UIColor(white: 0.5, alpha: 0.5).cgColor
imageView.layer.borderWidth = 0.5
imageView.anchor(top: topAnchor, leading: leadingAnchor, bottom: bottomAnchor, trailing: trailingAnchor)
}
}
}

I solved my own question. Here is the code you should be applying. First two methods are UIScrollView methods that comes free with UICollectionView, which by the way a subclass of UIScrollView. updatePageControl method is the one that I wrote just to keep code clean and simple. Hope this helps...
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
updatePageControl(scrollView: scrollView)
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
updatePageControl(scrollView: scrollView)
}
func updatePageControl(scrollView: UIScrollView) {
let pageNumber = round(scrollView.contentOffset.x / scrollView.bounds.size.width)
guard let count = foodCategory?.foods?.count else {return}
let currentPageNumber = Int(pageNumber) % count
pageControl.currentPage = currentPageNumber
}

Related

Why are my collectionview cells not animating?

I have collection view cells and I'd like to animate all of them at the same time. Even though I'm using UIView.animate the animation does not happen and affine transform happens instantaneously.
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
var collectionview: UICollectionView!
var moveText: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
collectionview = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
collectionview.translatesAutoresizingMaskIntoConstraints = false
collectionview.dataSource = self
collectionview.delegate = self
collectionview.backgroundColor = .systemBackground
collectionview.register(MyCell.self, forCellWithReuseIdentifier: "cell")
view.addSubview(collectionview)
NSLayoutConstraint.activate([
collectionview.topAnchor.constraint(equalTo: view.topAnchor),
collectionview.bottomAnchor.constraint(equalTo: view.bottomAnchor),
collectionview.leadingAnchor.constraint(equalTo: view.leadingAnchor),
collectionview.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionview.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MyCell
cell.moveText = moveText
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return .init(width: collectionview.frame.width, height: 120)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { timer in
UIView.animate(withDuration: 1, delay: 0) {
print("Starting animatings")
self.moveText = !self.moveText
self.collectionview.reloadData()
}
}
}
}
class MyCell: UICollectionViewCell {
let label = UILabel()
var moveText: Bool = false {
didSet {
if moveText == true {
label.transform = CGAffineTransform(translationX: 50, y: 50)
} else {
label.transform = CGAffineTransform(translationX: 0, y: 0)
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
label.text = "mytext"
label.translatesAutoresizingMaskIntoConstraints = false
addSubview(label)
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: leadingAnchor),
label.topAnchor.constraint(equalTo: topAnchor)
])
backgroundColor = .orange
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
This is because you are animating the wrong thing.
// adding weak self so the timer will not retain the view controller
Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { [weak self] timer in
self?.moveText.toggle() // you can use toggle() on a `Bool`
self?.collectionview.reloadData()
}
Instead, you should try to animate setting the value of individual cell
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionview.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MyCell
UIView.animate(withDuration: 1, delay: 0) {
cell.moveText = self.moveText
}
return cell
}
Also, instead of using CGAffineTransform(translationX: 0, y: 0) you could use .identity
label.transform = .identity
Worth noting is the fact that reloading collection view every 2 seconds and animating directly in cellForItemAt is very inefficient. You should find another way to pass information to the cell's they should animate the content

how to prevent delay when trying to animate invisible cells in collectionview

I would like to animate all of my collectionview cells and this simplified example illustrates the problem. The problem is that if I scroll down while animating cells the ones that are not visible gets animated with a delay. I would like invisible cells to just be in the final state of animation instead of having animation delay.
In the video you see that when I scroll to the bottom, the final cells starts to animate with delay.
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
var collectionview: UICollectionView!
var moveText: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
collectionview = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
collectionview.translatesAutoresizingMaskIntoConstraints = false
collectionview.dataSource = self
collectionview.delegate = self
collectionview.backgroundColor = .systemBackground
collectionview.register(MyCell.self, forCellWithReuseIdentifier: "cell")
view.addSubview(collectionview)
NSLayoutConstraint.activate([
collectionview.topAnchor.constraint(equalTo: view.topAnchor),
collectionview.bottomAnchor.constraint(equalTo: view.bottomAnchor),
collectionview.leadingAnchor.constraint(equalTo: view.leadingAnchor),
collectionview.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionview.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MyCell
UIView.animate(withDuration: 1, delay: 0, options: [
UIView.AnimationOptions.allowAnimatedContent,
UIView.AnimationOptions.allowUserInteraction,
]) {
cell.moveText = self.moveText
}
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return .init(width: collectionview.frame.width, height: 120)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
Timer.scheduledTimer(withTimeInterval: 1.5, repeats: true) { timer in
print("Starting animatings")
self.moveText.toggle()
self.collectionview.reloadData()
}
}
}
class MyCell: UICollectionViewCell {
let label = UILabel()
var moveText: Bool = false {
didSet {
if moveText == true {
label.transform = CGAffineTransform(translationX: 50, y: 50)
} else {
label.transform = .identity
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
label.text = "mytext"
label.translatesAutoresizingMaskIntoConstraints = false
addSubview(label)
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: leadingAnchor),
label.topAnchor.constraint(equalTo: topAnchor)
])
backgroundColor = .orange
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
instead of animating inside the cellForItemAt add this helper function.
private func animateVisibleCells() {
collectionview.visibleCells.compactMap { $0 as? MyCell }.forEach { cell in
UIView.animate(withDuration: 1, delay: 0, options: [
UIView.AnimationOptions.allowAnimatedContent,
UIView.AnimationOptions.allowUserInteraction,
]) {
cell.moveText = self.moveText
}
}
}
Now call it instead of reloading the collection view
Timer.scheduledTimer(withTimeInterval: 1.5, repeats: true) { timer in
print("Starting animatings")
self.moveText.toggle()
self.animateVisibleCells()
}
and convert the cellForItemAt implementation to:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionview.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MyCell
cell.moveText = moveText
return cell
}

Cannot assign value of type '(JournalListViewController) -> () -> JournalListViewController' to type 'JournalListViewController?'

I am trying to show another ViewController from a custom tab bar but, I cannot pass a view controller to the custom class.
I've tried var jlvc = HomeController() but that would crash the app.
class JournalListViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
override func viewDidLoad() {
super.viewDidLoad()
print(self)
navigationController?.navigationBar.isTranslucent = false
let titleLabel = UILabel(frame: CGRect(x: 0, y: 0, width: view.frame.width-32, height: view.frame.height))
titleLabel.textColor = UIColor.white
titleLabel.text = "Journal List"
titleLabel.font = UIFont.systemFont(ofSize: 20)
navigationItem.titleView = titleLabel
collectionView?.backgroundColor = UIColor.white
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Register cell class
collectionView?.register(JournalCell.self, forCellWithReuseIdentifier: "cellId")
// To unblock view from menubar
collectionView?.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 60, right: 0)
collectionView?.scrollIndicatorInsets = UIEdgeInsets(top: 0, left: 0, bottom: 60, right: 0)
// Do any additional setup after loading the view.
setupMenuBar()
}
let menuBar: MenuBar = {
let mb = MenuBar()
mb.jlvc = self
return mb
}()
private func setupMenuBar(){
view.addSubview(menuBar)
view.addConstraintsWithFormat(format: "H:|[v0]|", views: menuBar)
view.addConstraintsWithFormat(format: "V:[v0(60)]|", views: menuBar)
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
// MARK: UICollectionViewDataSource
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let height = (view.frame.width - 16 - 16) * 9 / 16 //-16 for top, - 16 for bot, 16/9 aspect ratio so * by 9/16
return CGSize(width:view.frame.width, height:height + 16 + 41) //height is height if image + additional space we added
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func showHomeController(){
let hc = HomeController()
navigationController?.pushViewController(hc, animated: true)
}
import Foundation
import UIKit
class MenuBar: UIView, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
var jlvc: JournalListViewController?
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = UIColor(red: 152/255, green: 216/255, blue: 142/255, alpha: 1)
cv.dataSource = self
cv.delegate = self
return cv
}()
let cellId = "cellId"
let imageNames = ["home","camera","journal"]
override init(frame: CGRect) {
super.init(frame: frame)
collectionView.register(MenuCell.self, forCellWithReuseIdentifier: cellId)
addSubview(collectionView)
addConstraintsWithFormat(format: "H:|[v0]|", views: collectionView)
addConstraintsWithFormat(format: "V:|[v0]|", views: collectionView)
let selectIndexPath = IndexPath(item: 0, section: 0)
collectionView.selectItem(at: selectIndexPath, animated: false, scrollPosition: [])
self.collectionView.isScrollEnabled = false
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
// Returning Cell
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! MenuCell
cell.imageView.image = UIImage(named: imageNames[indexPath.item])?.withRenderingMode(.alwaysTemplate)
cell.tintColor = UIColor.black
cell.jlvc = self.jlvc
return cell
}
// Size of Cell
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: frame.width / 3, height: frame.height)
}
// Spaceing in between cells
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
// Selecting cell
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! MenuCell
if cell.imageView.image == UIImage(named: "home")?.withRenderingMode(.alwaysTemplate) {
jlvc?.showHomeController()
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class MenuCell: BaseCell {
var jlvc: JournalListViewController?
let imageView: UIImageView = {
let iv = UIImageView()
iv.image = UIImage(named: "home")?.withRenderingMode(.alwaysTemplate)
iv.tintColor = UIColor.black
return iv
}()
override var isHighlighted: Bool {
didSet{
imageView.tintColor = isHighlighted ? UIColor.white : UIColor.black
}
}
override var isSelected: Bool {
didSet{
imageView.tintColor = isSelected ? UIColor.white : UIColor.black
}
}
override func setupViews() {
super.setupViews()
addSubview(imageView)
addConstraintsWithFormat(format: "H:[v0(28)]", views: imageView)
addConstraintsWithFormat(format: "V:[v0(28)]", views: imageView)
addConstraint(NSLayoutConstraint(item: imageView, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0))
addConstraint(NSLayoutConstraint(item: imageView, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0))
}
}
Cannot assign value of type '(JournalListViewController) -> () -> JournalListViewController' to type 'JournalListViewController?'
let menuBar: MenuBar = {
let mb = MenuBar()
mb.jlvc = self
return mb
}()
This doesn't do what you're thinking. self in this context is the class, not the instance. You either meant to make this a lazy var (as you did with collectionView), or to assign it explicitly.

UICollectionView Datasource methods do not get called

I am refactoring my ViewControllers and one of them contains a collectionView but now the DataSource is not getting called anymore.
My ViewController:
class CoinPageVC: UIViewController, DependencyInjectionVC, Storyboarded {
lazy var mainView: CoinPageV = {
let v = CoinPageV()
v.collectionView.delegate = self
return v
}()
var coin: Coin!
var selectedBase: String!
var viewContainer: [UIView]!
var collectionViewViewDataSource: CollectionViewCoinPageDatasource?
func injectDependencys(dependency: CoinPageDependency) {
self.coin = dependency.coin
self.selectedBase = dependency.base
self.viewContainer = dependency.views
}
override func viewDidLoad() {
super.viewDidLoad()
self.collectionViewViewDataSource = CollectionViewCoinPageDatasource(data: viewContainer)
self.mainView.collectionView.dataSource = self.collectionViewViewDataSource
self.mainView.collectionView.reloadData()
}
}
extension CoinPageVC: SetMainView {}
extension CoinPageVC: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width:CGFloat = collectionView.bounds.width
let height:CGFloat = collectionView.bounds.height
// - (tabBarHeight + menuBar.frame.height + heightNavigationBarTop)
let output = Utility.shared.CGSizeMake(width, height)
return output
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let index = Int(targetContentOffset.pointee.x / view.frame.width)
let indexPath = IndexPath(item: index, section: 0)
mainView.menuBar.collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredHorizontally)
}
}
My Datasource class:
class CollectionViewCoinPageDatasource: NSObject, UICollectionViewDataSource {
let data: [UIView]
init(data: [UIView]){
self.data = data
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
return data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
//let outputCell: UICollectionViewCell
let row = indexPath.item
let outputCell = collectionView.dequeueReusableCell(withReuseIdentifier: Identifier.coinPageCollectionViewOverviewCell.rawValue, for:indexPath) as! CollectionViewCellView
outputCell.view = data[row]
return outputCell
}
}
My collectionView setup:
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let frame = CGRect(x: 0, y: 0, width: 0, height: 0)
let cv = UICollectionView(frame: frame, collectionViewLayout: layout)
cv.register(CollectionViewCellView.self, forCellWithReuseIdentifier: Identifier.coinPageCollectionViewOverviewCell.rawValue)
if let flowLayout = cv.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.scrollDirection = .horizontal
flowLayout.minimumLineSpacing = 0
}
cv.backgroundColor = .green
cv.isPagingEnabled = true
//cv.backgroundColor = .blue
return cv
}()
What did I miss?
I set up the datasource and also connect it to the datasource of the collectionView, but the methods do not get called.
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let frame = CGRect(x: 0, y: 0, width: 0, height: 0)
let cv = UICollectionView(frame: frame, collectionViewLayout: layout)
cv.register(CollectionViewCellView.self, forCellWithReuseIdentifier: Identifier.coinPageCollectionViewOverviewCell.rawValue)
if let flowLayout = cv.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.scrollDirection = .horizontal
flowLayout.minimumLineSpacing = 0
}
cv.backgroundColor = .green
cv.isPagingEnabled = true
//cv.backgroundColor = .blue
cv.delegate = self
return cv
}()

scrollToItem horizontal scrolling collectionView crash

I am having an issue with implementing a horizontal scrollToItem collectionView. I have a collectionView (menuBar) that on tap of a cell the page collectionView will horizontally scroll to the appropriate cell. On tap of the menuBar collectionViewCell the app crashes. Not sure where the error is. Thanks in advance!
// controller class
class CurrentUserPlaceDetailsVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
var titleText: String?
let cellId = "cellId"
// UI elements
lazy var contentCollectionView: UICollectionView = {
let cv = UICollectionView()
cv.backgroundColor = UIColor.red
cv.layer.zPosition = 0
cv.translatesAutoresizingMaskIntoConstraints = false
cv.layer.masksToBounds = true
return cv
}()
override func viewDidLoad() {
super.viewDidLoad()
setupMenuBar()
setupCollectionView()
}
func scrollToMenuIndex(_ menuIndex: Int) {
let indexPath = IndexPath(item: menuIndex, section: 0)
contentCollectionView.scrollToItem(at: indexPath, at: UICollectionViewScrollPosition(), animated: true)
}
lazy var menuBar: MenuBar = {
let mb = MenuBar()
mb.currentUserPlaceDetailsVC = self
return mb
}()
private func setupMenuBar() {
view.addSubview(menuBar)
view.addConstraintsWithFormat("H:|[v0]|", views: menuBar)
view.addConstraintsWithFormat("V:|[v0(114)]", views: menuBar)
}
func setupCollectionView() {
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
// layout.sectionInset = UIEdgeInsets(top: 0, left: 10, bottom: 10, right: 10)
layout.itemSize = CGSize(width: self.view.frame.width, height: self.view.frame.height)
let contentCollectionView:UICollectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
contentCollectionView.dataSource = self
contentCollectionView.delegate = self
// register cells
contentCollectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cellId")
contentCollectionView.backgroundColor = .white
// contentCollectionView.scrollIndicatorInsets = UIEdgeInsets(top:114, left: 10, bottom: 10, right: 10)
self.view.addSubview(contentCollectionView)
_ = contentCollectionView.anchor(view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, topConstant: 114, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 0)
view.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
contentCollectionView.isPagingEnabled = true
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print(scrollView.contentOffset.x)
menuBar.horizontalBarLeftAnchorConstraint?.constant = scrollView.contentOffset.x / 4
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let myCell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath)
let colors: [UIColor] = [.blue, .red, .yellow, .green]
myCell.backgroundColor = colors[indexPath.item]
return myCell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: view.frame.height)
}
}
// menu bar class
class MenuBar: UIView, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 0
layout.headerReferenceSize = .zero
layout.sectionInset = .zero
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .green
cv.dataSource = self
cv.delegate = self
return cv
}()
let cellId = "cellId"
var currentUserPlaceDetailsVC: CurrentUserPlaceDetailsVC?
override init(frame: CGRect) {
super.init(frame: frame)
collectionView.register(MenuCell.self, forCellWithReuseIdentifier: cellId)
addSubview(collectionView)
addConstraintsWithFormat("H:|[v0]|", views: collectionView)
addConstraintsWithFormat("V:|[v0]|", views: collectionView)
let selectedIndexPath = IndexPath(item: 0, section: 0)
collectionView.selectItem(at: selectedIndexPath, animated: false, scrollPosition: UICollectionViewScrollPosition())
setupHorizontalBar()
}
var horizontalBarLeftAnchorConstraint: NSLayoutConstraint?
func setupHorizontalBar() {
let horizontalBarView = UIView()
horizontalBarView.backgroundColor = .white
horizontalBarView.translatesAutoresizingMaskIntoConstraints = false
addSubview(horizontalBarView)
horizontalBarLeftAnchorConstraint = horizontalBarView.leftAnchor.constraint(equalTo: self.leftAnchor)
horizontalBarLeftAnchorConstraint?.isActive = true
horizontalBarView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
horizontalBarView.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 1/4).isActive = true
horizontalBarView.heightAnchor.constraint(equalToConstant: 4).isActive = true
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print(indexPath.item)
let x = CGFloat(indexPath.item) * frame.width / 4
horizontalBarLeftAnchorConstraint?.constant = x
UIView.animate(withDuration: 0.75, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.layoutIfNeeded()
}, completion: nil)
currentUserPlaceDetailsVC?.scrollToMenuIndex(indexPath.item)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! MenuCell
cell.imageView.image = UIImage(named: imageNames[indexPath.item])?.withRenderingMode(.alwaysTemplate)
cell.tintColor = UIColor.rgb(91, green: 14, blue: 13)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: frame.width / 4, height: frame.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
In your comment you said:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'UICollectionView must be initialized with a non-nil layout parameter'. I believe there is something wrong in my func scrollToMenuIndex()... when I remove that func the app does not crash.
That happens, because you inside your View Controller your collectionView declared as lazy, that means it will be calculated on-demand.
The problem is in initializing your collectionView (reason: 'UICollectionView must be initialized with a non-nil layout parameter')
In your view controller replace UICollectionView initialization with this code:
lazy var contentCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = UIColor.red
cv.layer.zPosition = 0
cv.translatesAutoresizingMaskIntoConstraints = false
cv.layer.masksToBounds = true
return cv
}()
P.S. As I wrote in my comment to your question, you have reference cycle. class MenuBar should hold weak reference to your view controller to prevent this.