Making animating horizontal indicator using collectionView - ios

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

Related

How to connect UIPageControl and CollectionView?

I have collection view with Custom View Cell. There are scroll view and three image view in a custom view cell.
My ViewController has UIPageControll, but I don't know how to connect UIPageControll and scroll view.
My code
ViewController:
class MainScrenenViewController: UIViewController {
let data = [
CustomData(title: "A", backgroundImage: #imageLiteral(resourceName: "Onboard")),
CustomData(title: "B", backgroundImage: #imageLiteral(resourceName: "Onboard")),
CustomData(title: "B", backgroundImage: #imageLiteral(resourceName: "Onboard")),
]
//UIPage Controller
lazy var pageControl: UIPageControl = {
let pageControl = UIPageControl()
pageControl.numberOfPages = data.count
pageControl.translatesAutoresizingMaskIntoConstraints = false
pageControl.addTarget(self, action: #selector(pageControlTapHandler(sender:)), for: .touchUpInside)
return pageControl
}()
var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.register(DayWeatherCell.self, forCellWithReuseIdentifier: "sliderCell")
collectionView.layer.cornerRadius = 5
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.backgroundColor = UIColor(red: 0.125, green: 0.306, blue: 0.78, alpha: 1)
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .brown
view.addSubview(collectionView)
view.addSubview(pageControl)
collectionView.dataSource = self
collectionView.delegate = self
setupConstraints()
}
//MARK: ~FUNCTIONS
func setupConstraints() {
let constraints = [
collectionView.widthAnchor.constraint(equalToConstant: 344),
collectionView.heightAnchor.constraint(equalToConstant: 212),
collectionView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16),
collectionView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16),
collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 112),
pageControl.topAnchor.constraint(equalTo: cityLabel.bottomAnchor, constant: 10),
pageControl.widthAnchor.constraint(equalToConstant: 100),
pageControl.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
]
NSLayoutConstraint.activate(constraints)
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
//Selector for UIPage Controller
#objc func pageControlTapHandler(sender: UIPageControl) {
//I don't know what I need to do here
}
extension MainScrenenViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "sliderCell", for: indexPath) as! DayWeatherCell
cell.backgroundColor = .red
return cell
}
}
extension MainScrenenViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("User tapped on item \(indexPath.row)")
}
}
extension MainScrenenViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
}
}
extension MainScrenenViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: collectionView.frame.height)
}
}
My CollectionViewCell:
class DayWeatherCell: UICollectionViewCell, UIScrollViewDelegate {
weak var mainScreenViewController: MainScrenenViewController?
var data: CustomData? {
didSet {
guard let data = data else { return }
imageView.image = data.backgroundImage
}
}
var imageView: UIImageView = {
let imageView = UIImageView()
imageView.image = #imageLiteral(resourceName: "Onboard")
imageView.layer.cornerRadius = 5
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
lazy var scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.showsHorizontalScrollIndicator = false
scrollView.isPagingEnabled = true
scrollView.delegate = self
return scrollView
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(scrollView)
contentView.addSubview(imageView)
self.contentView.layer.cornerRadius = 10
let constraints = [
scrollView.topAnchor.constraint(equalTo: contentView.topAnchor),
scrollView.leftAnchor.constraint(equalTo: contentView.leftAnchor),
scrollView.rightAnchor.constraint(equalTo: contentView.rightAnchor),
scrollView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
imageView.topAnchor.constraint(equalTo: contentView.topAnchor),
imageView.leftAnchor.constraint(equalTo: contentView.leftAnchor),
imageView.rightAnchor.constraint(equalTo: contentView.rightAnchor),
imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
]
NSLayoutConstraint.activate(constraints)
}
required init?( coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
enter image description here
You can use this. In this code block, collection view cell size equal to collection view and collection view scroll horizontally. I hope, this helps you.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offSet = scrollView.contentOffset.x
let width = scrollView.frame.width
let horizontalCenter = width / 2
pageControl.currentPage = Int(offSet + horizontalCenter) / Int(width)
}

SubViews are not adding in some UICollectionViewCells and flickering (programmatically)

I am trying to make custom Image Slider with collections view. I want to it to be reusable. So I made separate custom class where all collectionView stuff. and then call that class from UIViewController as shown in code below. And my UICollectonViewCell only contains imageView.
Problem is that in second cell. imageView is not being added and on third cell, it also flickers. I tried to debug these issues but could not.
ImageSlider class and UICollectionViewCell class at end end, with collection view stuff:
class ImageSlider: UIView {
var imgArr = [UIImage(named: "one.jpg"), UIImage(named: "3.jpg"), UIImage(named: "two.jpg"), UIImage(named: "4.jpg")]
var sliderCollection : UICollectionView = {
let widthRatio : Float = 16.0
let heightRatio : Float = 9.0
let collecionWidth = UIScreen.main.bounds.width - 30
let layout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: CGRect(x: 15, y: 100, width: collecionWidth, height: CGFloat((Float(collecionWidth)*heightRatio)/widthRatio)), collectionViewLayout: layout)
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
collectionView.backgroundColor = .systemOrange
collectionView.isPagingEnabled = true
// collectionView.isScrollEnabled = true
collectionView.register(ImageSliderCell.self, forCellWithReuseIdentifier: "ImageSliderCell")
return collectionView
}()
}
extension ImageSlider: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imgArr.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageSliderCell", for: indexPath) as! ImageSliderCell
cell.imgView.image = imgArr[indexPath.item]
// cell.imgView.contentMode = .scaleAspectFit
print("cell frame : ", "(\(cell.frame.width), \(cell.frame.height)")
print("imgView frame : ", "(\(cell.imgView.frame.width), \(cell.imgView.frame.height)")
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: collectionView.frame.height)
}
}
class ImageSliderCell: UICollectionViewCell {
var imgView = UIImageView()
// override func awakeFromNib() {
// self.addSubview(imgView)
// }
override init(frame: CGRect) {
super.init(frame: frame)
imgView.frame = frame
self.addSubview(imgView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
This is ViewController, where I am calling ImageSlider() class.
class ImageSliderVC: UIViewController, UICollectionViewDelegate {
let imageSlider = ImageSlider()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(imageSlider.sliderCollection)
imageSlider.sliderCollection.delegate = imageSlider
imageSlider.sliderCollection.dataSource = imageSlider
imageSlider.sliderCollection.reloadData()
}
}
It looks like it does not work without constrains because UICollectionViewCell could be created with zero frame and it translated to imageView inside the cell. You need to add constrains to imageView to make it visible.
extension UIView {
func centerX(inView view: UIView, constant: CGFloat = 0) {
translatesAutoresizingMaskIntoConstraints = false
centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: constant).isActive = true
}
func centerY(inView view: UIView, constant: CGFloat = 0) {
translatesAutoresizingMaskIntoConstraints = false
centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: constant).isActive = true
}
func setDimensions(height: CGFloat, width: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
heightAnchor.constraint(equalToConstant: height).isActive = true
widthAnchor.constraint(equalToConstant: width).isActive = true
}
func setHeight(_ height: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
heightAnchor.constraint(equalToConstant: height).isActive = true
}
}
class ImageSliderCell: UICollectionViewCell {
var imgView = UIImageView()
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(imgView)
// not sure about the right size of image ...
imgView.setDimensions(height: 100.0, width: 100.0)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Cells won't show in UICollectionView

I'm trying to create a UICollectionView and display few cells there.
This is my code:
class MainVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
var mForecast = [CustomCollectionViewCell]()
let CVCellIdentifier = "forecastCell"
lazy var mCollectionView: UICollectionView = {
var collectionView = UICollectionView(frame: CGRect(x: 0, y: 0, width: 300, height: 150), collectionViewLayout: UICollectionViewFlowLayout())
collectionView.clipsToBounds = true
collectionView.backgroundColor = .red
collectionView.translatesAutoresizingMaskIntoConstraints = false
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(red: 80/255, green: 135/255, blue: 179/255, alpha: 1.0)
setupNavBar()
self.navigationItem.searchController = mSearchBarController
setupMainWeatherIcon()
fillArrayWithData()
mCollectionView.register(CustomCollectionViewCell.self, forCellWithReuseIdentifier: CVCellIdentifier)
mCollectionView.dataSource = self
mCollectionView.delegate = self
}
private func fillArrayWithData(){
for _ in 1...6 {
let forecastCell: ForecastCell = ForecastCell()
forecastCell.mDayLabel = "DAY-TEST"
forecastCell.mWeatherIcon = UIImage(named: "partly-cloudy")
forecastCell.mTempLabel = "TEMP-TEST"
mForecast.append(forecastCell)
}
mCollectionView.reloadData()
}
//MARK: COLLECTION VIEW METHODS
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return mForecast.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = mCollectionView.dequeueReusableCell(withReuseIdentifier: CVCellIdentifier, for: indexPath) as! CustomCollectionViewCell
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: (view.frame.width / 6) - 16 , height: 70)
}
func collectionView(_ collectionView: UICollectionView, layout
collectionViewLayout: UICollectionViewLayout, insetForSectionAt
section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 10, left: 8, bottom: 10, right: 8)
}
}
This is the CustomCollectionViewCell class:
import UIKit
class CustomCollectionViewCell: UICollectionViewCell {
var mDayLabel: String?
var mWeatherIcon: UIImage?
var mTempLabel: String?
let dayTV: UILabel = {
var label = UILabel()
label.textAlignment = .center
label.font = UIFont.boldSystemFont(ofSize: 12)
label.textColor = .blue
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let weatherImg: UIImageView = {
var img = UIImageView()
img.translatesAutoresizingMaskIntoConstraints = false
return img
}()
let tempLabel: UILabel = {
var label = UILabel()
label.textAlignment = .center
label.font = UIFont.systemFont(ofSize: 8)
label.textColor = .blue
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
if let label = mDayLabel{
dayTV.text = label
}
if let image = mWeatherIcon{
weatherImg.image = image
}
if let temp = mTempLabel{
tempLabel.text = temp
}
setupDayTextView()
setupWeatherImage()
setupTempLabel()
}
private func setupDayTextView(){
addSubview(dayTV)
dayTV.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
dayTV.topAnchor.constraint(equalTo: self.topAnchor, constant: 10).isActive = true
}
private func setupWeatherImage(){
addSubview(weatherImg)
weatherImg.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
weatherImg.topAnchor.constraint(equalTo: dayTV.bottomAnchor, constant: 10).isActive = true
}
private func setupTempLabel(){
addSubview(tempLabel)
tempLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
tempLabel.topAnchor.constraint(equalTo: weatherImg.bottomAnchor, constant: 10).isActive = true
}
}
Of course I have a method to fill mForecast array with data, if I add a subview I do see the cell, but I know I shouldn't do that to view the cells inside the collection view.
I tried looking but couldn't find what's wrong here and why the cells won't be displayed.
This is what I get
ORIGINAL:
After setting the delegate and the datasource, you need to call collectionView.reloadData()
REVISED:
You are calling fillArrayWithData, which calls reloadData before you finish configuring the collectionView's datasource and delegate. Thus, when reloadData is called, there is no source that sets the data and loads the cells.
Try calling your fillArrayWithData after you finalize the configuration of your collection view.
I personally recommend configuring your collection view in viewDidLoad or in the didSet property observer of collectionView:
var collectionView: UICollectionView! {
didSet {
collectionView.delegate = self
collectionView.dataSource = self
}
}
And then I initiate the load of the data in my viewWillAppear method.
EXAMPLE:
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(red: 80/255, green: 135/255, blue: 179/255, alpha: 1.0)
setupNavBar()
self.navigationItem.searchController = mSearchBarController
setupMainWeatherIcon()
// This is where you are calling fillArrayWithData right now.
// fillArrayWithData()
mCollectionView.register(CustomCollectionViewCell.self, forCellWithReuseIdentifier: CVCellIdentifier)
mCollectionView.dataSource = self
mCollectionView.delegate = self
// This is where you should be calling fillArrayWithData
fillArrayWithData()
}

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
}

ImageView of a custom CollectionViewCell is nil when it should be configured

I have a tableViewCell with a collectionView, collectionView's cells are custom ones, they contains just a imageView.
Here is my test project
Here are DataSource required methods from my CollectionView class:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCell", for: indexPath) as! ImageCell
let image = UIImage(named: listItems[indexPath.row])
cell.testImageView.image = image
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return listItems.count
}
When I try to set image for cell's imageView I get this error:
fatal error: unexpectedly found nil while unwrapping an Optional value
I have checked image, it isn't nil, but testImageView is, I get this error when I try to set image to collectionViewCell's testImageView.
How can I fix it?
EDIT1
Here is method called from tableViewController to fill collectionView's listItem
func load(listItem: [String]) {
self.listItems = listItem
reloadData()
}
Also if I remove code from collectionView cellForItemAt indexPath with this one all is working fine
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCell", for: indexPath)
let imageView = UIImageView(image:UIImage(named: listItems[indexPath.row]))
cell.backgroundView = imageView
You have mistaken your two view controllers. Your IB outlet is connected to a cell in a different view controller. I mean you can have multiple views in different controllers connected to a same IBOutlet, but in your case the one that loads first is not connected, so that is why it crashes.
This is the cell your outlet was connected to.
This is that you are trying to load (but did not connect IBOutlet to image view):
Just in case you want to use code instead..
import UIKit
class ImageCell : UICollectionViewCell {
private var imageView: UIImageView!
private var descLabel: UILabel!
public var image: UIImage? {
get {
return self.imageView.image
}
set {
self.imageView.image = newValue
}
}
public var imageDesc: String? {
get {
return self.descLabel.text
}
set {
self.descLabel.text = newValue
}
}
override init(frame: CGRect) {
super.init(frame: frame)
self.initControls()
self.setTheme()
self.doLayout()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.initControls()
self.setTheme()
self.doLayout()
}
override func awakeFromNib() {
super.awakeFromNib()
}
func initControls() {
self.imageView = UIImageView()
self.descLabel = UILabel()
}
func setTheme() {
self.imageView.contentMode = .scaleAspectFit
self.descLabel.numberOfLines = 1
self.descLabel.lineBreakMode = .byWordWrapping
self.descLabel.textAlignment = .center
self.descLabel.textColor = UIColor.black
self.contentView.backgroundColor = UIColor.white
}
func doLayout() {
self.contentView.addSubview(self.imageView)
self.contentView.addSubview(self.descLabel)
self.imageView.leftAnchor.constraint(equalTo: self.contentView.leftAnchor, constant: 5).isActive = true
self.imageView.rightAnchor.constraint(equalTo: self.contentView.rightAnchor, constant: -5).isActive = true
self.imageView.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 0).isActive = true
self.descLabel.leftAnchor.constraint(equalTo: self.contentView.leftAnchor, constant: 5).isActive = true
self.descLabel.rightAnchor.constraint(equalTo: self.contentView.rightAnchor, constant: -5).isActive = true
self.descLabel.topAnchor.constraint(equalTo: self.imageView.bottomAnchor, constant: 5).isActive = true
self.descLabel.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -5).isActive = true
for view in self.contentView.subviews {
view.translatesAutoresizingMaskIntoConstraints = false
}
}
}
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
private var collectionView: UICollectionView!
private var dataSource: Array<String>!
override func viewDidLoad() {
super.viewDidLoad()
self.initDataSource()
self.initControls()
self.setTheme()
self.registerClasses()
self.doLayout()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func initDataSource() {
self.dataSource = ["Image1", "Image2", "Image3", "Image4", "Image5", "Image6"]
}
func initControls() {
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: 117, height: 125)
layout.invalidateLayout()
self.collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
self.collectionView.delegate = self
self.collectionView.dataSource = self
}
func setTheme() {
self.collectionView.backgroundColor = UIColor.clear
self.edgesForExtendedLayout = UIRectEdge(rawValue: 0)
self.view.backgroundColor = UIColor.blue
}
func registerClasses() {
self.collectionView.register(ImageCell.self, forCellWithReuseIdentifier: "ImageCellIdentifier")
}
func doLayout() {
self.view.addSubview(self.collectionView)
self.collectionView.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
self.collectionView.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
self.collectionView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
self.collectionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
for view in self.view.subviews {
view.translatesAutoresizingMaskIntoConstraints = false
}
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.dataSource.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCellIdentifier", for: indexPath) as! ImageCell
let imageName = self.dataSource[indexPath.row]
cell.image = UIImage(named: imageName)
cell.imageDesc = imageName
return cell
}
}
http://imgur.com/o7O7Plw
maybe the "testImageView" outlet variable is not connected from the interface builder or there is no CollectionViewCell with reuseIdentifier "ImageCell". Check whether cell is nil or not using LLDB po command.

Resources