UICollectionViewCell could not dequeue a view of kind : UICollectionElementKindCell with identifier - swift

I am following Duc Tran's Tutorial on a collection (https://www.youtube.com/watch?v=vB-HKnhOgl8) and when I am running my application, I keep getting this error:
Thread 1: Exception: "could not dequeue a view of kind: UICollectionElementKindCell with identifier CHALLENGECELL - must register a nib or a class for the identifier or connect a prototype cell in a storyboard"
I looked at other solutions for this but none have yielded results.
Here is the code for the view controller that holds the collection
import UIKit
class ChallengesViewerViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
//MARK: - UICollectionViewDataSource
private var challenges = WeeklyChallenges.createChallenge()
let cellScaling: CGFloat = 1.0
override func viewDidLoad() {
super.viewDidLoad()
let screenSize = UIScreen.main.bounds
let cellWidth = floor(screenSize.width * cellScaling)
let cellHeight = floor(screenSize.height * cellScaling)
let insetX = (view.bounds.width - cellWidth)/2
let insetY = (view.bounds.height - cellHeight)/2
let layout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
layout.itemSize = CGSize(width: cellWidth, height: cellHeight)
collectionView.contentInset = UIEdgeInsets(top: insetY, left: insetX, bottom: insetY, right: insetX)
collectionView.dataSource = self
}
}
extension ChallengesViewerViewController: UICollectionViewDataSource{
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return challenges.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CHALLENGECELL", for: indexPath) as! ChallengeCollectionViewCell
cell.challenge = self.challenges[indexPath.item]
return cell
}
}
Additionally, here is the storyboard where I am assigning the reusable identifier to the collection view cell.

You have to register the cell that you're dequeuing in cellForItem in viewDidLoad.
override func viewDidLoad() {
super.viewDidLoad()
//...
collectionView.register(ChallengeCollectionViewCell.self, forCellWithReuseIdentifier: "CHALLENGECELL")
}
If you're using Nib use this instead:
let nib = UINib(nibName: <#T##String#>, bundle: nil)
collectionView.register(nib, forCellWithReuseIdentifier: "CHALLENGECELL")

Related

Updating height constraint of UICollectionView not working

I have this collection view inside my collection view cell, of which I will populate with more cells. I'm currently trying to update the height of the UICollectionView upon any editing events to the inner cells text field.
This currently works when I load the page, click into the field and then scroll. However, if I load the page, scroll first and then click into the field, it doesn't work but I'm unsure why. I can't see anything that is running differently.
Any help would be appreciated. Thanks.
import UIKit
class MyCollectionViewCell: UICollectionViewCell {
var heightAnchor: NSLayoutConstraint?
var collectionView: UICollectionView!
static let reuseIdentifier: String = "MyCollectionViewCell"
override func awakeFromNib() {
super.awakeFromNib()
var layout: UICollectionViewLayout = UICollectionViewLayout()
collectionView = UICollectionView(frame: contentView, collectionViewLayout: layout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.isScrollEnabled = false
self.contentView.addSubview(collectionView)
heightAnchor = collectionView.heightAnchor.constraint(equalToConstant: 600)
heightAnchor?.isActive = true
collectionView.anchorViewPosition(parent: self.contentView, leading: .zero, top: .zero, bottom: .zero)
collectionView.register(UINib(nibName: "MyInnerCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: MyInnerCollectionViewCell.reuseIdentifier)
}
override func layoutSubviews() {
super.layoutSubviews()
collectionview.widthAnchor.constraint(equalToConstant: 288).isActive = true
}
#objc func updateCollectionViewSize() {
heightAnchor?.constant = 200
}
}
extension MyCollectionViewCell: UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyInnerCollectionViewCell", for: indexPath) as? MyInnerCollectionViewCell
cell?.field.addTarget(self, action: #selector(updateCollectionViewSize()), for: .allEditingEvents)
return cell!
}
}

How can I update a UICollectionView from another class?

I am trying this for a couple of days now and haven't achieved anything yet. What I am trying to do is when a user picks a User item form the ViewController class, I want to save it in Realm and show it in the CollectionView of the SavedInterestsViewController class. I use a delegate pattern as suggested in this post How to access and refresh a UITableView from another class in Swift, but unfortunately I still receive nil, I guess because the GC removed the collectionView outlet already right? (please correct me if I misunderstood it). However, how can I get this to work by using a delegate pattern? Here is my code, this is the class where the user Picks a new User-item:
protocol ViewControllerDelegate {
func didUpdate(sender: ViewController)
}
class ViewController: UIViewController {
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var collectionView: UICollectionView!
var delegate: ViewControllerDelegate?
let numberOfTweets = 5
let realm = try! Realm()
var image = UIImage()
var imageArray: [String] = []
var userArray: [User] = []
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(UINib(nibName: "CollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "CollectionCell")
searchBar.delegate = self
}
}
extension ViewController: UISearchBarDelegate {
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
print("One second...")
let functionClass = NetworkingFunctions()
var loopCount = 0
functionClass.getWikipediaAssumptions(for: searchBar.text!) { [self] (articleArray) in
self.userArray.removeAll()
for x in articleArray {
functionClass.performWikipediaSearch(with: x, language: WikipediaLanguage("en")) { (user) in
self.userArray.append(user)
collectionView.reloadData()
loopCount += 1
}
}
}
}
}
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return userArray.count
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionCell", for: indexPath) as! CollectionViewCell
let image = UIImage.init(data: userArray[indexPath.item].image as Data)
cell.userImage.image = image
cell.nameLabel.text = userArray[indexPath.item].name
cell.userImage.layer.borderColor = image?.averageColor?.cgColor
if userArray[indexPath.item].checked == false {
cell.checkmark.isHidden = true
} else {
cell.checkmark.isHidden = false
}
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if userArray[indexPath.item].checked == false {
userArray[indexPath.item].checked = true
collectionView.reloadData()
let newUser = User()
newUser.image = userArray[indexPath.item].image
newUser.name = userArray[indexPath.item].name
newUser.checked = true
try! realm.write {
realm.add(newUser)
}
self.delegate = SavedInterestsViewController()
self.delegate?.didUpdate(sender: self)
}
else {
userArray[indexPath.item].checked = false
collectionView.reloadData()
}
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = UIScreen.main.bounds.width/3 - 10
let height = width
return CGSize(width: width, height: height)
}
}
class User: Object {
#objc dynamic var image: NSData = NSData()
#objc dynamic var name: String = ""
#objc dynamic var checked: Bool = false
}
... and this is the class where I want to show the selected item, after the User clicked on the 'Back' Button of the navigation controller of the ViewController class:
class SavedInterestsViewController: UIViewController, ViewControllerDelegate {
func didUpdate(sender: ViewController) {
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
#IBOutlet weak var addButton: UIBarButtonItem!
#IBOutlet weak var collectionView: UICollectionView!
let realm = try! Realm()
var userArray: [User] = []
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(UINib(nibName: "CollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "CollectionCell")
fetchDataFromRealm()
}
#IBAction func addButtonPressed(_ sender: UIBarButtonItem) {
performSegue(withIdentifier: "SavedToNew", sender: self)
}
func fetchDataFromRealm() {
userArray.append(contentsOf: realm.objects(User.self))
collectionView.reloadData()
}
}
extension SavedInterestsViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return userArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionCell", for: indexPath) as! CollectionViewCell
let image = UIImage.init(data: userArray[indexPath.item].image as Data)
cell.userImage.image = image
cell.nameLabel.text = userArray[indexPath.item].name
cell.userImage.layer.borderColor = image?.averageColor?.cgColor
if userArray[indexPath.item].checked == false {
cell.checkmark.isHidden = true
} else {
cell.checkmark.isHidden = false
}
return cell
}
}
extension SavedInterestsViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = UIScreen.main.bounds.width/3 - 10
let height = width
return CGSize(width: width, height: height)
}
}
in your code you have lines
self.delegate = SavedInterestsViewController()
self.delegate?.didUpdate(sender: self)
but it do mostly nothing - you set the delegate to newly created class, didUpdate it - but I don't see any use of it - you don't present it in any way.
If I understand you right - you have SavedInterestsViewController, from it - you open ViewController, do something and when back to SavedInterestsViewController. (I can be wrong with your flow - correct me if so)
In this flow - you have delegate property in ViewController, but it must be of type SavedInterestsViewController. And you have to set it to SavedInterestsViewController when you open ViewController from it. And later in ViewController you have to call didUpdate method of delegate.

UIcollectionView not updating the size and layout of UIcollectionCell upon phone rotation

I have a UIcollectionView with images. When I rotate the screen, the cells do not resize as expected. I have tried adding collectionView.collectionViewLayout.invalidateLayout() to the viewDidLayoutSubviews() - but this only causes a crash. Please can someone advise?
I have a UIcollectionView with images. When I rotate the screen, the cells do not resize as expected. I have tried adding collectionView.collectionViewLayout.invalidateLayout() to the viewDidLayoutSubviews() - but this only causes a crash. Please can someone advise?
Portrait:
Landdscape:
class GridPicksCollectionViewController: UICollectionViewController{
let cellId = "gridyPickCell"
var numCelPerRow: CGFloat = 3
var layout: UICollectionViewFlowLayout!
let borderInset: CGFloat = 3
let interCellSpacing: CGFloat = 3
let spacingBetweenRows: CGFloat = 3
var dataSource = [UIImage]()
var collectionViewWidth: CGFloat!
override func viewDidLoad() {
super.viewDidLoad()
generalSetup()
setUpDateSource()
setupCollectionViewLayout()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print("viewDidLayoutSubviews")
// Update collectionViewWidth upon rotation
collectionViewWidth = self.collectionView.frame.width
//collectionView.collectionViewLayout.invalidateLayout() - causes app to crash
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
print("viewWillTransition")
}
private func generalSetup(){
navigationController?.navigationBar.barTintColor = UIColor.appDarkGreen
navigationItem.title = "Girdy Picks"
let textAttributes = [NSAttributedString.Key.foregroundColor:UIColor.white]
navigationController?.navigationBar.titleTextAttributes = textAttributes
collectionView.backgroundColor = UIColor.white
collectionViewWidth = collectionView.frame.width
// Make sure collectionView is always within the safe area layout
if #available(iOS 11.0, *) {
collectionView?.contentInsetAdjustmentBehavior = .always
}
// Register UICollectionViewCell
collectionView.register(GirdyPickCollectionCell.self, forCellWithReuseIdentifier: cellId)
}
private func setUpDateSource(){
let images: [UIImage] = [UIImage.init(named: "buddha")!, UIImage.init(named: "sharpener")!, UIImage.init(named: "cars")!, UIImage.init(named: "houses")!, UIImage.init(named: "houses2")!, UIImage.init(named: "tower1")!, UIImage.init(named: "tower2")!]
dataSource = images
}
private func setupCollectionViewLayout(){
collectionView.delegate = self
collectionView.dataSource = self
layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout
layout.minimumInteritemSpacing = interCellSpacing // distance between cells in a row
layout.minimumLineSpacing = spacingBetweenRows // distance in between rows
layout.sectionInset = UIEdgeInsets.init(top: borderInset, left: borderInset, bottom: borderInset, right: borderInset) // border inset for collectionView
}
}
extension GridPicksCollectionViewController: UICollectionViewDelegateFlowLayout{
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataSource.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! GirdyPickCollectionCell
cell.imageView.image = dataSource[indexPath.row]
return cell
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let selectedImage = dataSource[indexPath.row]
let showImageVC = ImageGridViewController()
showImageVC.modalPresentationStyle = .fullScreen
showImageVC.imageToDisplay = selectedImage
self.present(showImageVC, animated: true) {}
}
// Determine size for UICollectionCell
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
print("sizeForItemAt")
guard let collectionWidth = collectionViewWidth else{ return CGSize.init()}
let widthCell = (collectionWidth - interCellSpacing * 2 - borderInset * 2) / numCelPerRow
return CGSize.init(width: widthCell, height: widthCell)
}
}
You are wrong about your lifecycle of events. When the device is rotated, your view hierarchy becomes notified of this event, and views start to re-layout themselves based on the information they have. After this has been finished, viewDidLayoutSubviews() will be called, not before.
Since you are updating the collectionViewWidth property in this method, the layout still uses the old value when calling collectionView(_:layout:sizeForItemAt:). You need to set this value in viewWillLayoutSubviews().
Alternatively, you can write a setter for this property which will call invalidateLayout() on the collection view's layout, but this will cause an unnecessary layout pass.

CollectionViewCell not displaying cells or loading core data string

I am trying to save a core data string via the first class, then load that string into a collectionviewcell textlabel. I am doing all of this without the use of any storyboards. I am not getting any compile errors. It is just when I load this class, nothing is appearing.
This is a link to my git profile https://github.com/redrock34/collectionViewCellLoad.
class ViewController: UIViewController {
var itemsName: [Item] = []
weak var collectionView: UICollectionView!
override func loadView() {
super.loadView()
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
collectionView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(collectionView)
NSLayoutConstraint.activate([
self.view.topAnchor.constraint(equalTo: collectionView.topAnchor),
self.view.bottomAnchor.constraint(equalTo: collectionView.bottomAnchor),
self.view.leadingAnchor.constraint(equalTo: collectionView.leadingAnchor),
self.view.trailingAnchor.constraint(equalTo: collectionView.trailingAnchor),
])
self.collectionView = collectionView
}
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView.dataSource = self
self.collectionView.delegate = self
self.collectionView.register(Cell.self, forCellWithReuseIdentifier: Cell.identifier)
self.collectionView.alwaysBounceVertical = true
self.collectionView.backgroundColor = .brown
collectionView.reloadData()
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return self.itemsName.count
}
func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Cell.identifier, for: indexPath) as! Cell
let user = itemsName[indexPath.row]
cell.textLabel?.text = ("\nCourse: \(user.atBATS!) Score: ")
cell.backgroundColor = UIColor.blue
cell.textLabel.textColor = UIColor.black
return cell
}
}
You have an empty dataSource
var itemsName: [Item] = []

Subviews are nil when UICollectionView and UICollectionViewCell are in XIB files

Hi there! I've created a sample project that shows my problem exactly. Click to download!
I've created an app which contains a subclass of UICollectionViewCell, it contains an ImageView as subview. It also has a XIB file containing the view.
When I use this UICollectionViewCell within a UICollectionView that is directly embedded in a ViewController, the cell works just fine. Here's the code of the WorkingViewController containing the code that registers the cell:
class WorkingViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
let cellNib = UINib(nibName: "ImageCell", bundle: nil)
collectionView.register(cellNib, forCellWithReuseIdentifier: "ImageCell")
collectionView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func randomInt(min: Int, max: Int) -> Int {
return Int(arc4random_uniform(UInt32(max - min + 1))) + min
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 100
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCell", for: indexPath) as? ImageCell else {
fatalError("Could not load cell")
}
let randomNumber = randomInt(min: 1, max: 10)
cell.imageView.image = UIImage(named: "stockphoto\(randomNumber)")
return cell
}
}
Now move the Storyboard Entry Point over to the BuggyViewController. Here the UICollectionView is not directly embedded. It is contained within a UIView that also has an XIB. Here's the code for that view, containing the registering of the cell from earlier:
class DetailView: UIView, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet var contentView: UIView!
#IBOutlet weak var collectionView: UICollectionView!
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
private func setup() {
Bundle.main.loadNibNamed("DetailView", owner: self, options: nil)
self.addSubview(self.contentView)
self.contentView.frame = self.bounds
self.autoresizingMask = [.flexibleWidth, .flexibleHeight]
collectionView.delegate = self
collectionView.dataSource = self
let cellNib = UINib(nibName: "ImageCell", bundle: nil)
collectionView.register(cellNib, forCellWithReuseIdentifier: "ImageCell")
collectionView.register(ImageCell.self, forCellWithReuseIdentifier: "ImageCell")
collectionView.reloadData()
}
func randomInt(min: Int, max: Int) -> Int {
return Int(arc4random_uniform(UInt32(max - min + 1))) + min
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 100
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCell", for: indexPath) as? ImageCell else {
fatalError("Could not load cell")
}
let randomNumber = randomInt(min: 1, max: 10)
cell.imageView.image = UIImage(named: "stockphoto\(randomNumber)")
return cell
}
}
But this results in an error in the DetailView, line 55:
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an
Optional value
So here the imageView of the cell is suddenly nil which makes me think I did something wrong registering the cell or instantiating it. But I just cannot resolve it.
You should delete the line where you register the cell class:
collectionView.register(ImageCell.self, forCellWithReuseIdentifier: "ImageCell")
Indeed you've defined your cell in a Nib and should register it through the method func register(_ nib: UINib?, forCellWithReuseIdentifier identifier: String). Registering it through the cellClass method will return nil.
And as the Apple doc states:
If you previously registered a class or nib file with the same reuse
identifier, the class you specify in the cellClass parameter replaces
the old entry. You may specify nil for cellClass if you want to
unregister the class from the specified reuse identifier.
This explains why you were getting nil.