Swift - could not dequeue a view of kind: UICollectionElementKindCell - ios

I got this error message when trying to load my project
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'could not dequeue a view of kind: UICollectionElementKindCell with identifier CustomCell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'
My code is:
extension ViewController: JTAppleCalendarViewDelegate, JTAppleCalendarViewDataSource {
func configureCalendar(_ calendar: JTAppleCalendarView) -> ConfigurationParameters {
formatter.dateFormat = "yyyy MM dd"
formatter.timeZone = Calendar.current.timeZone
formatter.locale = Calendar.current.locale
let startDate = formatter.date(from: "2017 01 01")!
let endDate = formatter.date(from: "2017 12 31")!
let parameters = ConfigurationParameters(startDate: startDate, endDate: endDate)
return parameters
}
func calendar(_ calendar: JTAppleCalendarView, cellForItemAt date: Date, cellState: CellState, indexPath: IndexPath) -> JTAppleCell {
let cell = calendar.dequeueReusableJTAppleCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.dateLabel.text = cellState.text
return cell
}
}
Please help me debug this problem. Thank you.
Edit: I have added the identifier for the cell, but still the error exists.

In viewDidLoad method:
You must register your custom cell class/xib name with collectionView object.
If you have only class custom class then you register by following way (No xib file)
collectionView.register(CustomCell.self, forCellWithReuseIdentifier: "cellId")
If you have both class as well as xib file then you can register by this way
collectionView.register(UINib(nibName: "CustomCell", bundle: nil), forCellWithReuseIdentifier: "cellId")

It is because you haven't registered your xib to UICollectionView. If you are using UICollectionViewCell from Xib, you must register it first.
In viewDidLoad:
write it:
if let xib = NSNib.init(nibNamed: "TemplateNBgCollectionItem", bundle: nil) {
self.collectionView.register(xib, forItemWithIdentifier: NSUserInterfaceItemIdentifier(rawValue: "cvItem"))
}

From the code snippets you pasted, I don't see you register the cell before you want to dequeue/use it. Need to register the cell before use as the error described.
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "DefaultCell")
For your quick reference, please see below post -
https://www.hackingwithswift.com/example-code/uikit/how-to-register-a-cell-for-uitableviewcell-reuse
Edit after screenshot posted(Please accept this answer if works for you ^_^)
From the screenshot, you are using a custom cell class 'CustomCell', after you set the correct identifier, please make sure setup the correct class name as CustomCell in the identity inspector of right panel. Refer to below screenshot.

If you use cell in storyboard not XIB then you need to set reuseIDentifier as below
If you use separate XIB for cell then you need to add this line in ViewDidLoad
collectionView.register(UINib(nibName: "NibName", bundle: nil), forCellWithReuseIdentifier: "reuseIdentifier")

There are many possibilities for this issue. If you have custom class for collection view then it should be set in identity inspector. And it must be subclass of UICollectionViewCell. And you have to instantiate that subclass in your cellforitem delegate method with proper reusable identifier that must be set in attribute inspector. Here in your screen shot, It seems like Apple calender view is your collection view. If it is third party library then make sure that it is allowing you to deque your custom cells! and make sure it is subclass of UICollectionView.

In My Case
Im creating a collection View with custom cell with class
This Is my Custom CollectionView Class Code
import UIKit
class Auction_CollectionCell: UICollectionViewCell {
#IBOutlet weak var Productimage: UIImageView!
#IBOutlet weak var name: UILabel!
#IBOutlet weak var bid: UILabel!
#IBOutlet weak var progress: UIProgressView!
#IBOutlet weak var day: UILabel!
#IBOutlet weak var hours: UILabel!
#IBOutlet weak var mins: UILabel!
#IBOutlet weak var secs: UILabel!
#IBOutlet weak var lblday: UILabel!
#IBOutlet weak var lblhours: UILabel!
#IBOutlet weak var lblmin: UILabel!
#IBOutlet weak var lblsecs: UILabel!
#IBOutlet weak var MainView: UIView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
}
Connect all the view from your storyboard to Custom Class by CTRL+Drag and #IBOutlet will be created.
I Called my custom class like this
self.AuctionList = NSArray with NSDictionary Content
func collectionView(_ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return self.AuctionList.count
}
func collectionView(_ collectionView: UICollectionView,cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell : Auction_CollectionCell = (collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCell", for: indexPath) as? Auction_CollectionCell)!
cell.lblday.text = "Day"
cell.lblhours.text = "Hours"
cell.lblmin.text = "Minutes"
cell.lblsecs.text = "Secs"
cell.MainView.layer.cornerRadius = 5
cell.MainView.backgroundColor = UIColor.white
cell.MainView.layer.borderWidth = 0.5
cell.MainView.layer.borderColor = UIColor.gray.cgColor
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
var width = CGFloat(0)
var height = CGFloat(0)
if (UIDevice.current.userInterfaceIdiom == .pad){
width = CGFloat((self.view.bounds.size.width / 4.0) - 15)
height = CGFloat(246.0)
}else{
width = CGFloat((self.view.bounds.size.width / 2.0) - 15)
height = CGFloat(246.0)
}
print("Resize Image \(width) \(height)")
return CGSize(width: width, height: height)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let dic = AuctionList[indexPath.row] as? NSDictionary{
Product.product_id = "\((dic["product_id"])!)"
Product.product_image = "\((dic["image"])!)"
Product.auction_id = "\((dic["auction_id"])!)"
performSegue(withIdentifier: "auctionProductDetails", sender: self)
}
}
Have fun And Good Luck.
Feel Free to edit my answer

I had the exact same issue with mine. and after looking around i realised i had a lower case "p" when it should've been an upper case "P" with the name i used for an identifier.
click on the collectionView cell on the storyboard, open the right panel. Check attributes inspector and collection Reusable View, identifier name, but also check the identity inspector, custom class name.

Related

How to reload views on UIViewController on demand?

I have a UIViewController with some labels and a UICollectionView. A UICollectionView represents a self-made calendar, loaded from xib. When I tap on one of the collection view cells (say, choose a day in calendar), I change my underlying model accordingly, and I need the labels to update. But nothing gets updated on tap.
I have a method that updates the labels and call it in viewDidLoad(). After first loading all labels become nil, and declaring labels as 'var' and not as 'weak var' doesn't help. When calling some of the functions on viewController's self.view like layoutSubviews(), labels seem to exist, viewDidLoad() (and updateLabels()) gets called, but nothing changes on the screen.
I've tried to create (subclass) a UIView with labels using xib and programmatically, placed the labels directly in viewController, putting all the code in one place - same result.
setNeedsDisplay(), life cycle methods, removing-adding to superview - none of this worked for me. Am I doing something wrong or missing something important?
class ChallengeViewController: UIViewController {
var challenge = Challenge(title: Challenge.sampleTitle, startDate: Challenge.sampleStartDate, durationInDays: 40, checkIns: [Bool](repeating: true, count: 40), cheatDaysPlanned: 2, isActive: true, roundNumber: 1, pointsScored: 0)
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var calendarView: CalendarView!
#IBOutlet weak var statisticsView: ChallengeStatisticsView!
//contains labels that should be updated
override func viewDidLoad() {
super.viewDidLoad()
calendarView.challenge = challenge
calendarView.updateMonthAndYearLabels(for: challenge)
statisticsView.updateLabels()
titleLabel.text = challenge.title
}
}
class CalendarView: UIView {
var challenge: Challenge?
//other code here
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "calendarCell", for: indexPath) as! CalendarCollectionViewCell
collectionView.performBatchUpdates({
if let challenge = challenge {
cell.update(challenge: challenge)
let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ChallengeScreenViewController") as! ChallengeViewController
viewController.view.layoutSubviews()
viewController.challenge = challenge
viewController.updateLabels()
collectionView.reloadItems(at: [indexPath])
}
}, completion: nil)
}
}
You're creating a brand new view controller in your cell.update and it's never on the screen, which is why you're not seeing any updates. Not sure what your update method actually does anyway but it seems like just calling self.updateLabels() or self.statisticsView.updateLabels() should do the trick. Not sure which since you seem to have both.
Why are you reloading your VC?
When you click on a collectionViewCell, create a delegate-protocol to tell the VC to reload the labels with the datamodel.
Use cellForItem(at:) method to get the selected cell
To update the view in the current view controller, just call the update method. Do not create a new view controller.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! CalendarCollectionViewCell
if let challenge = challenge {
cell.update(challenge: challenge)
collectionView.reloadItems(at: [indexPath])
self.updateLabels()
}
}
I didn't know that UIStoryboard(name:, bundle:).instantiateViewController(withIdentifier:) creates a brand new vc, that was the main mistake.
So I've decided to use Notification Center to make the controller respond to changes in model.
Everything works now.
class CalendarView: UIView {
var challenge: Challenge?
//other code here
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "calendarCell", for: indexPath) as! CalendarCollectionViewCell
collectionView.performBatchUpdates({
if let challenge = challenge {
cell.update(challenge: challenge)
// assigning a new value to calendarView's own variable, posting the notification
self.challenge = challenge
let name = Notification.Name("CellTapped")
NotificationCenter.default.post(name: name, object: nil)
collectionView.reloadItems(at: [indexPath])
}
}, completion: nil)
}
}
class ChallengeViewController: UIViewController {
var challenge = Challenge(title: Challenge.sampleTitle, startDate: Challenge.sampleStartDate, durationInDays: 40, checkIns: [Bool](repeating: true, count: 40), cheatDaysPlanned: 2, isActive: true, roundNumber: 1, pointsScored: 0)
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var calendarView: CalendarView!
#IBOutlet weak var statisticsView: ChallengeStatisticsView!
// taking the changed variable from calendarView when the notification is posted
#objc func updateAfterTap() {
if let calendarViewChallenge = calendarView.challenge {
self.challenge = calendarViewChallenge
statisticsView.updateLabels()
}
}
override func viewDidLoad() {
super.viewDidLoad()
calendarView.challenge = challenge
calendarView.updateMonthAndYearLabels(for: challenge)
statisticsView.updateLabels()
titleLabel.text = challenge.title
NotificationCenter.default.addObserver(self, selector: #selector(self.updateAfterTap), name: Notification.Name("CellTapped"), object: nil)
}
}

Button inside CollectionView not clickable

I have a button in a custom cell of a collectionview. The collectionview is on a scrollview. For some reason, I am not able to click on the button. I've checked that all my elements have User Interaction enabled.
Here is my layout of the collection (I've hidden some sensitive data)
Here is my custom collection view cell:
class MyCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var connectButton: UIButton!
var onConnectTap: (MyCollectionViewCell) -> Void)?
#IBAction func connectButton(_ sender: Any) {
onConnectTap?(self)
}
func populate(_ user: User) {
nameLabel.text = user.name
}
}
I have a xib file where a Touch Up Inside event of a button has been hooked up to the connectButton IBAction.
And in my ViewController:
MyCollectionView.register(UINib(nibName: "MyCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "cell")
Here's my collection view function in my ViewController:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = myCollectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MyCollectionViewCell
let user = users.values[indexPath.row]
cell.populate(user)
cell.onConnectTap = { (cell) in
//do something
}
return cell
}
Nothing happens when I click on the button. Am I missing something here? Is the scroll view interfering? Do I need to specifiy a addTarget? Or something else?
After searching the entire web pretty much, I finally found the solution that was in the comment of this SO answer: https://stackoverflow.com/a/44908916/406322
I needed to add this in MyCollectionViewCell:
self.contentView.isUserInteractionEnabled = false
I think the cell selection was hijacking the touch event.
I'm facing the same issue and found the best solution after spending much time.
cell.contentView.isUserInteractionEnabled = false
But its the perfect solution, and adds only one line in the cell for item method
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = myCollectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MyCollectionViewCell
cell.contentView.isUserInteractionEnabled = false
return cell
}
Double-check the structure of the XIB file. I lost time dealing with this issue (where the button in the XIB did not seem to respond), as the structure had a second embedded cell, rather than just one (AboutCell in my case).

Indexpath.row inside custom cell class

How can I figure out the indexpath.row of an active cell in a function that's inside the custom cell class?
I use this:
protocol ItemTableViewCellDelegate: NSObjectProtocol {
func textFieldDidEndEditing(text: String, cell: ItemTableViewCell)
}
class ItemTableViewCell: UITableViewCell, UITextFieldDelegate {
#IBOutlet weak var itemTitle: UITextField!
#IBOutlet weak var date: UILabel!
var delegate: ItemTableViewCellDelegate?
override func awakeFromNib() {
itemTitle.delegate = self
}
func textFieldDidEndEditing(_ textField: UITextField) {
if let text = textField.text {
delegate?.textFieldDidEndEditing(text: text, cell: self)
}
//BTW: everything below this comment runs every time I want it to, so no problem about that
items.insert(itemTitle.text!, at: //(Here I want the indexpath))
}
}
So, I want to update my array as the textfield's change. In order to do that, I need to figure out the index path.row. I tried putting it in as so:
items.insert(itemTitle.text!, at: indexPath.row)
But it doesn't let me do that.
If this isn't possible to do in the cell class, I'm open to ideas how it could be done inside the main class, too.
a screenshot of my view:
Add an IndexPath variable in your ItemTableViewCell class
var indexPathForCell: IndexPath?
And in your parent view controller class cellForRowAtIndexPath:-
let cell = tableView.dequeueReusableCell(withIdentifier: "yourIdentifier", for: indexPath) as! ItemTableViewCell
cell.indexPathForCell = indexPath
return cell

Can´t add items to UICollectionView inside UIView xib

Objective
I wanna place my (BusinessViewTableHeader: UIView) as tableView header:
tableView.tableHeaderView = BusinessViewTableHeader.instanceFromNib() as! BusinessViewTableHeader
Inside BusinessViewTableHeader there is a UICollectionView which are supposed to display images when swiped, much like the Tinder app.
This is my UIView subclass:
class BusinessViewTableHeader: UIView {
#IBOutlet var collectionView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
self.collectionView.delegate = self
self.collectionView.registerNib(UINib(nibName: "BusinessImageCollectionCell", bundle: nil), forCellWithReuseIdentifier: "BusinessImageCollectionCell")
}
class func instanceFromNib() -> UIView {
return UINib(nibName: "BusinessViewTableHeader", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as! UIView
}
....
}
extension BusinessViewTableHeader: UICollectionViewDelegate, UICollectionViewDataSource {
....
}
Problem
I have a custom UIView xib containing a UICollectionView. The problem is that I can´t add any cells (items) to the UICollectionView. I can add items to my other UICollectionView which are placed inside a UIViewController. The first image is showing the properties for the UICollectionView inside a UIViewController, the second image is showing the UICollectionView inside a UIView xib.
[![UICollectionView in UIViewController][1]][1]
[1]: http://i.stack.imgur.com/zFCeG.png
[![UICollectionView in UIView xib][2][2]
[2]: http://i.stack.imgur.com/jKU6z.png
Question
Why am I not able to add items to the UICollectionView inside the UIView xib? How?
You can't have UICollectionViewCell when the UICollectionView is on a Nib. What you need to do is to create the UICollectionViewCell as another nib and get it registered in the class that you are using for your CollectionView.
Create a new nib, drag a UICollectionViewCell inside it, and do something like this in the class that works with your UICollectionView.
override func awakeFromNib() {
let nibName = UINib(nibName: "ClassCollectionCell", bundle:nil)
collectionView.registerNib(nibName, forCellWithReuseIdentifier: "collectionCell")
}
Remember you can add a custom class to the UICollectionViewCell so you can pass dynamic data to it.
Adding cells in a xib is not supported. If you must use a xib file, then you will need a separate xib which contains the UICollectionView cell. Storyboards may be a better solution.
It is not clear what you are trying to achieve. UICollectionView has specific means for creating headers which uses the datasource and delegate. Collection views are good for displaying items in a grid layout or other complex arrangements.
If all you need is to display a list of rows, then a UITableViewController might be an easier alternative.
Whatever the case, it is probably better to use a storyboard instead of a xib, and to subclass the UICollectionViewController or UITableViewController, rather than a subview.
Your custom class name can be entered in the identity inspector for the UIViewController or UIView:
In Swift 3.0 register nib with following method-
let nibName = UINib(nibName: "FruitCell", bundle:nil)
collectionView.register(nibName, forCellWithReuseIdentifier: "CellIdentifier")
In Swift 3.0
first Create Xibfile of UIView
import UIKit
class SubCatagoryListView:UIView , UICollectionViewDelegate , UICollectionViewDataSource
{
#IBOutlet weak var mainView: UIView!
#IBOutlet weak var btnClose: UIButton!
#IBOutlet weak var subCategoryListCollectionView: UICollectionView!
#IBOutlet weak var lblTitle: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
subCategoryListCollectionView.register(UINib(nibName: "SubcatagoryListCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "SubcatagoryListCollectionViewCell")
mainView.layer.cornerRadius = 10.0
mainView.clipsToBounds = true
}
static func subCatagoryListView() -> SubCatagoryListView? {
let arr = Bundle.main.loadNibNamed("SubCatagoryListView", owner: self, options: nil)
if arr != nil {
if arr!.count > 0 {
if let view = arr![0] as? SubCatagoryListView {
return view;
}
}
}
return nil;
}
#IBAction func btnBackgroundTapped(_ sender: UIButton)
{
self.removeFromSuperview()
}
#IBAction func btnCloseTapped(_ sender: UIButton)
{
self.removeFromSuperview()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SubcatagoryListCollectionViewCell", for: indexPath) as! SubcatagoryListCollectionViewCell
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize {
let cellsize = CGSize(width: (subCategoryListCollectionView.bounds.size.width/3) - 10, height: 50)
return cellsize
}
}
After Create new CollectionViewCell Xib file
import UIKit
class SubcatagoryListCollectionViewCell: UICollectionViewCell {
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
}
after create both file i am load xib on my storybord
var subcatagoryXib:SubCatagoryListView?
override func awakeFromNib() {
super.awakeFromNib()
if let subcategoryView = SubCatagoryListView.subCatagoryListView()
{
subcatagoryXib = subcategoryView
}
}
#IBAction func btnAddInterestTapped(_ sender: UIButton) {
if subcatagoryXib != nil
{
self.subcatagoryXib!.frame = CGRect(x:0, y: 0, width: self.view.frame.width , height: self.view.frame.height)
self.view.addSubview(self.subcatagoryXib!)
}
}
Same as #Pato's answer, but here is a more thorough tutorial for how to add a customized UICollectionViewCell inside a Xib file from #aestusLabs on Medium. It's a 3-5 min reading, I personally find it very helpful. It basically tells you to create another customized UICollectionViewCell with .xib, and register it in your "level 1" cell's awakeFromNib().
https://medium.com/#aestusLabs/adding-a-uicollectionviews-to-a-custom-uitableviewcell-xib-tutorial-swift-4-xcode-9-2-1ec9ce4095d3

UICollectionView + custom cell

please help me with custom cell in collection
i have an error:
..this class is not key value coding-compliant for the key cell_label..
you can download full project here:
https://drive.google.com/file/d/0B1nXU0xmeKg8WTRYRXJVbWdMVlU/view?usp=sharing
i want to show collection with custom cell in modalview
PS: i use xcode 7 beta and swift 2 and separate xib for every view
import UIKit
class ModalController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
#IBOutlet weak var myCollection: UICollectionView!
#IBOutlet weak var myCell: colywCell!
var tableData: [String] = ["XXX", "YYY", "ZZZ"]
override func viewDidLoad() {
super.viewDidLoad()
self.title="modal W"
myCollection.dataSource = self
myCollection.delegate = self
let nipName=UINib(nibName: "colywCell", bundle:nil)
myCollection.registerNib(nipName, forCellWithReuseIdentifier: "cell1")
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return tableData.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
self.myCell = myCollection.dequeueReusableCellWithReuseIdentifier("cell1", forIndexPath: indexPath) as! colywCell
self.myCell.Cell_label.text = tableData[indexPath.row]
return self.myCell
}
}
and custom cell file
import UIKit
class colywCell: UICollectionViewCell {
#IBOutlet weak var Cell_label: UILabel!
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
you need to aware of who the owner is, and which class should be loaded with the nib.
in nutshell, you need to aware of those in your colywCell.nib:
1a.
make sure that the File's Owner is not a custom class.
1b.
make sure to disconnect every outlet from File's Owner.
2a.
make sure that the actual UICollectionViewCell is your custom class.
2b.
optionally connect the outlets to your custom class.
This is a common error, it usually means that you have an Outlet set on your view that is not binded to any IBOutlet on your Cell.
This happens when you bind a view with some var in your class and then you remove that var and not it's bind on your xib.

Resources