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.
Related
I'm trying to create a horizontal collection view inside a tableviewcell, with a dynamic size uiimageview inside, which will be the mandatory view.
I created a UIView :
public class CardImage: UIView {
private var imageView: UIImageView?
private var titleLabel: UILabel?
private var descriptionLabel: UILabel?
private var subtitleLabel: UILabel?
private var icon: UIImage?
private var imageURL: String?
private var screenPercentage: CGFloat = 1.0
override public func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = 4
}
public override func awakeFromNib() {
super.awakeFromNib()
setup()
layoutSubviews()
}
public init() {
super.init(frame: .zero)
setup()
layoutSubviews()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
layoutSubviews()
}
public func fill(dto: SomeDTO) {
setupImage(icon: dto.image, imageUrl: dto.imageURL)
descriptionLabel?.text = dto.description
titleLabel?.text = dto.title
subtitleLabel?.text = dto.subtitle
screenPercentage = dto.screenPercentage
}
private func setup() {
isUserInteractionEnabled = true
translatesAutoresizingMaskIntoConstraints = false
backgroundColor = .red
titleLabel = UILabel()
titleLabel?.textColor = .white
titleLabel?.numberOfLines = 1
addSubview(titleLabel!)
descriptionLabel = UILabel()
descriptionLabel?.textColor = .white
descriptionLabel?.numberOfLines = 2
addSubview(descriptionLabel!)
subtitleLabel = UILabel()
subtitleLabel?.textColor = .white
subtitleLabel?.numberOfLines = 1
addSubview(subtitleLabel!)
imageView = UIImageView(frame: .zero)
imageView?.backgroundColor = .red
addSubview(imageView!)
setupConstraints()
}
private func setupImage(icon: UIImage?, imageUrl: String?) {
if let url = imageURL {
return
}
guard let image = icon else {
return
}
imageView?.image = image
imageView?.contentMode = .scaleAspectFit
setNeedsDisplay()
setNeedsLayout()
}
private func setupConstraints() {
imageView?.translatesAutoresizingMaskIntoConstraints = false
imageView?.topAnchor.constraint(equalTo: topAnchor).isActive = true
imageView?.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
imageView?.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
let screenSize = UIScreen.main.bounds
let computedWidth = screenSize.width * screenPercentage
imageView?.widthAnchor.constraint(equalToConstant: computedWidth).isActive = true
//the image should be 16:9
imageView?.heightAnchor.constraint(equalTo: widthAnchor, multiplier: 9.0/16.0).isActive = true
titleLabel?.translatesAutoresizingMaskIntoConstraints = false
titleLabel?.topAnchor.constraint(equalTo: imageView!.bottomAnchor, constant: 16).isActive = true
titleLabel?.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16).isActive = true
titleLabel?.heightAnchor.constraint(equalToConstant: 18).isActive = true
subtitleLabel?.translatesAutoresizingMaskIntoConstraints = false
subtitleLabel?.topAnchor.constraint(equalTo: titleLabel!.topAnchor).isActive = true
subtitleLabel?.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16).isActive = true
titleLabel?.widthAnchor.constraint(equalTo: subtitleLabel!.widthAnchor).isActive = true
titleLabel?.trailingAnchor.constraint(equalTo: subtitleLabel!.leadingAnchor, constant: 6).isActive = true
descriptionLabel?.translatesAutoresizingMaskIntoConstraints = false
descriptionLabel?.topAnchor.constraint(equalTo: titleLabel!.bottomAnchor, constant: 16).isActive = true
descriptionLabel?.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16).isActive = true
descriptionLabel?.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16).isActive = true
descriptionLabel?.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -16).isActive = true
}
}
Which will be inside a CollectionViewCell:
public class CardImageCollectionViewCell: UICollectionViewCell {
private let view: CardImage = CardImage()
override public func awakeFromNib() {
super.awakeFromNib()
translatesAutoresizingMaskIntoConstraints = false
//this only pin the view to the four anchors of the uicollectionview
view.pinToBounds(of: self.contentView)
backgroundColor = .clear
AccessibilityManager.setup(self, log: true)
}
public func fill(dto: SomeDTO) {
view.fill(dto: dto)
}
}
And then the CollectionViewCell, inside a tableviewcell, which has a collectionview:
public class CardImageCollectionView: UIView, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
#IBOutlet private weak var collectionView: UICollectionView!
private var dto: [SomeDTO] = []
public override func awakeFromNib() {
super.awakeFromNib()
setup()
}
private func setup() {
backgroundColor = .blue
collectionView.backgroundColor = .clear
collectionView.delegate = self
collectionView.dataSource = self
if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
}
registerCells()
}
func fill(dto: [SomaImageCardDTO]) {
self.dto = dto
DispatchQueue.main.async {
self.collectionView.reloadData()
self.collectionView.layoutIfNeeded()
}
}
private func registerCells() {
collectionView.register(UINib(nibName: String(describing: CardImageCollectionViewCell.self), bundle: nil), forCellWithReuseIdentifier: String(describing: CardImageCollectionViewCell.self))
}
public func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dto.count
}
//this should not be setted since the sizing is automatic
// public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// return CGSize(width: 304, height: 238)
// }
//
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: CardImageCollectionViewCell.self), for: indexPath) as! CardImageCollectionViewCell
cell.fill(dto: dto[indexPath.row])
return cell
}
}
The two problems I'm facing is, the image cannot assume any size, since the collection view doesn't have any size until it's filled with some info.
And then, even if I set the image size, I cannot pass the info to the UITableViewcell size.
If I understand your problem correctly, you have more than one rows in an UITableView which have horizontal UICollectionViews. Those collection views have cells which have dynamic size based on the images inside them.
So the width of the UITableView is fixed, but the height for the row depends on the height of the UICollectionView, correct?
I would recommend using self-sizing cells in the UITableView. See the reference here:
https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithSelf-SizingTableViewCells.html
After that, you need to find a way to calculate the height of the UICollectionView correctly, so the UITableView cell can determine the correct height. There are several ways to do it, for example by overriding the intrinsicContentSize property of the UICollectionView and returning the largest height of the images.
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
}
}
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()
}
What do i have: A view controller (WhereViewController) with some "child/container"-views. One is a collection view (typeCollectionView). the WhereViewController is embedded in a navigationController:
Screenshot
class WhereViewController: UIViewController {
lazy var progressContainerView: ProgressBarView = {
let containerView = ProgressBarView(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
containerView.translatesAutoresizingMaskIntoConstraints = false
return containerView
}()
let questionLabel: UILabel = {
let label = UILabel()
label.text = "question?"
label.textAlignment = NSTextAlignment.center
label.font = UIFont.init(name: "OpenSans-Italic", size: 14)
label.textColor = UIColor(red:0.33, green:0.33, blue:0.33, alpha:1.0)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let typeCollectionView: WhereCollectionView = {
let collectionView = WhereCollectionView(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
collectionView.translatesAutoresizingMaskIntoConstraints = false
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Schaden melden"
view.backgroundColor = UIColor.white
view.addSubview(progressContainerView)
view.addSubview(questionLabel)
view.addSubview(typeCollectionView)
setupProgressContainerView()
setupQuestionLabel()
setupTypeCollectionView()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
private func setupProgressContainerView() {
progressContainerView.topAnchor.constraint(equalTo: view.topAnchor, constant: 60).isActive = true
progressContainerView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
progressContainerView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
progressContainerView.heightAnchor.constraint(equalToConstant: 50).isActive = true
}
private func setupQuestionLabel() {
questionLabel.topAnchor.constraint(equalTo: progressContainerView.bottomAnchor, constant: 22).isActive = true
questionLabel.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
questionLabel.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
questionLabel.heightAnchor.constraint(equalToConstant: 20).isActive = true
}
private func setupTypeCollectionView() {
typeCollectionView.topAnchor.constraint(equalTo: questionLabel.bottomAnchor, constant: 20).isActive = true
typeCollectionView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
typeCollectionView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
typeCollectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
}
Want am I trying to do: Within the typeCollectionView: If an item is selected (didSelectItemAt) i want to push to the next viewController via navigationController of the WhereViewController. My typeCollectionView looks like:
class WhereCollectionView: UICollectionView, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
var whereViewController: WhereViewController?
let label = ["x", "y", "z"]
override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
let layout = UICollectionViewFlowLayout()
super.init(frame: CGRect.zero, collectionViewLayout: layout)
backgroundColor = UIColor.white
delegate = self
dataSource = self
self.register(WhereCollectionViewCell.self, forCellWithReuseIdentifier: "WhereCollectionViewCell")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return label.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "WhereCollectionViewCell", for: indexPath) as! WhereCollectionViewCell
cell.injureLabel.text = label[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: self.frame.width, height: 75)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
}
}
I've tried to give the whereViewController to the typeCollectionView:
let typeCollectionView: WhereCollectionView = {
let collectionView = WhereCollectionView(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.whereViewController = self
return tableView
}()
But get: Cannot assign value of type '(NSObject) -> () -> WhereViewController' to type 'WhereViewController?'
Could someone help me? or isn't it possible to "speak" with the navigationController from the collectionView (containerView)?
I do not use storyboard
Try to:
1) Add extension:
extension UIView {
var parentViewController: UIViewController? {
var parentResponder: UIResponder? = self
while parentResponder != nil {
parentResponder = parentResponder!.next
if parentResponder is UIViewController {
return parentResponder as! UIViewController!
}
}
return nil
}
}
2) Now you can use:
parentViewController?.present(<ControllerName>, animated: true)
Hope it helps
You should conform the view controller to the collection delegate methods and not the collection view itself.
Make WhereViewController conform to UICollectionViewDataSource, UICollectionViewDelegate and UICollectionViewDelegateFlowLayout:
class WhereViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout
In WhereViewController's viewDidLoad method, set up the delegate and the data source:
override func viewDidLoad() {
super.viewDidLoad()
typeCollectionView.dataSource = self
typeCollectionView.delegate = self
[...]
}
Here you can use delegate to send message from CollectionView to
ViewController.
Simple declare a protocol
protocol CustomDelegate: class {
func collectionView(DidSelect : Int)
}
After that make an instance of CustomDelegate protocol in WhereCollectionView Class
class WhereCollectionView: UICollectionView {
weak var customDelegate:CustomDelegate?
}
and in didSelect methods fire the delegate method
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let _del = customDelegate {
_del.collectionView(DidSelect : IndexPath.row)
}
}
In ViewController there you are adding collectionview in viewcontroller set delegate to self
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Schaden melden"
view.backgroundColor = UIColor.white
typeCollectionView.customDelegate = self
view.addSubview(progressContainerView)
view.addSubview(questionLabel)
view.addSubview(typeCollectionView)
setupProgressContainerView()
setupQuestionLabel()
setupTypeCollectionView()
}
Also Implement the Custom delegate method in ViewController
func collectionView(DidSelect : Int) {
/// from there you can do your job
}
I'm trying to pass an image from a view controller to a UICollectionViewCell whenever I segue to the UICollectionViewController. Normally, I would just use the following code to pass the variable directly to the UICollectionViewController.
let myCollectionViewController = MyCollectionViewController(collectionViewLayout: UICollectionViewFlowLayout())
myCollectionViewController.selectedImage = myImageView?.image
navigationController?.pushViewController(myCollectionViewController, animated: true)
However, I have subclassed my UICollectionViewCell and have set up the cell as follows:
import UIKit
class myCollectionViewCell: UICollectionViewCell {
let imageView:UIImageView = {
let iv = UIImageView()
iv.backgroundColor = .red
iv.contentMode = .scaleAspectFill
iv.clipsToBounds = true
return iv
}()
var selectedImage: UIImage? {
didSet {
self.imageView.image = selectedImage
}
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
How do I directly pass my image directly to my UICollectionViewCell subclass during the segue?
Hope this helps you-:
import Foundation
class HomeController: UIViewController{
// STORED VARIABLE FOR CollectionView
lazy var CollectionView : UICollectionView = {
var layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 0
var collectionViews = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionViews.translatesAutoresizingMaskIntoConstraints = false
collectionViews.backgroundColor = UIColor.white
collectionViews.showsHorizontalScrollIndicator = false
collectionViews.showsVerticalScrollIndicator = false
collectionViews.dataSource = self
collectionViews.delegate = self
return collectionViews
}()
// APPLY CONSTRAINTS FOR CollectionView
func setUpCollectionView(){
view.addSubview(CollectionView)
CollectionView.register(HomeControllerCell.self, forCellWithReuseIdentifier: "cell")
CollectionView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
CollectionView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
CollectionView.topAnchor.constraint(equalTo: view.topAnchor,constant:92).isActive = true
CollectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor,constant:-50).isActive = true
}
override func viewDidLoad() {
super.viewDidLoad()
setUpCollectionView()
}
}
// EXTENSION FOR COLLECTION VIEW PARENT
extension HomeController:UICollectionViewDataSource,UICollectionViewDelegate,UICollectionViewDelegateFlowLayout{
// NUMBER OF SECTION IN TABLE
public func numberOfSections(in collectionView: UICollectionView) -> Int{
return 1
}
// NUMBER OF ROWS IN PARENT SECTION
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int{
return 5
}
// The cell that is returned must be retrieved from a call to -dequeueReusableCellWithReuseIdentifier:forIndexPath:
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell{
let Cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! HomeControllerCell
// PASS IMAGE (whatever you have) TO COMPUTED VARIABLE image
Cell.image = pass image here
return Cell
}
// SIZE FOR PARENT CELL COLLECTION VIEW
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize{
return CGSize(width: view.frame.width, height: 220)
}
}
CollectionViewCellClass-:
class HomeControllerCell: UICollectionViewCell {
//INITIALIZER
override init(frame: CGRect) {
super.init(frame: frame)
setUpview()
}
// STORED VARIABLE imageVIEW
let imageView:UIImageView = {
let iv = UIImageView()
iv.backgroundColor = .red
iv.contentMode = .scaleAspectFill
iv.translatesAutoresizingMaskIntoConstraints = false
iv.clipsToBounds = true
return iv
}()
// COMPUTED VARIABLE image
var image: UIImage? {
didSet {
self.imageView.image = image
}
}
// APPLY CONSTRAINTS FOR CELL VIEWS
func setUpview(){
// ADD imageView AS SUBVIEW
addSubview(imageView)
// APPLY CONSTRAINT ON IMAGE VIEW
imageView.leftAnchor.constraint(equalTo: self.leftAnchor,constant:5).isActive = true
//menuHeader.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
imageView.topAnchor.constraint(equalTo: self.topAnchor,constant:12).isActive = true
imageView.heightAnchor.constraint(equalToConstant: 50).isActive = true
imageView.widthAnchor.constraint(equalToConstant: 50).isActive = true
}
// REQUIRED INIT
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}