I've seen lots of posts about this error, but none of the provided solutions have worked for me. I created the table through the storyboard with a custom prototype cell that I have created a class for. I set the Identifier on the cell.
Cell Identity Inspector: https://i.stack.imgur.com/ebkjA.png
Cell Attributes Inspector: https://i.stack.imgur.com/dIRxu.png
Storyboard table: https://i.stack.imgur.com/UAkeS.png
Code to dequeue cell:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "FNListViewCell",for: indexPath) as? FNListViewCell
//filling cell data
// Configure the cell...
return cell!
}
EDIT: I have tried 2 different things to get it to work, which both are getting rid of the error but are instead resulting in the table appearing empty when the page appears.
1) I tried registering the cell with the table using:
self.tableView.register(FNListViewCell.self, forCellReuseIdentifier: "FNListViewCell")
2) I changed the dequeue statement to
let cell = tableView.dequeueReusableCell(withIdentifier: "FNListViewCell") as? FNListViewCell
Both resulted in the cell object having its UILabel elements as null, but after initializing those and setting values I get a blank table.
If you have created a separate XIB for table view cell and a custom class for it, then you will have to register it before using it. If you are using table view in a view controller and have an outlet for your table view, then add this line in viewDidLoad().
tableView.register(UINib.init(nibName: "NameOfYourClass", bundle: nil), forCellReuseIdentifier: "YourIdentifier")
I found the problem and solution. I was programmatically modal presenting the table view using
let ModalController = FileNoteListTableViewController()
self.present(ModalController,animated: true,completion: nil)
I changed it to using a segue defined in the storyboard setup as Present Modally and now the dequeue is working. I'm guessing it is something to do with the controller not being initialized properly when I was doing it manually.
Related
I have used a tableView (myTableView) on a usual UIViewController class. I wish to use reusable cells in this tableView to save memory. I did not create separate XIB and dragged and dropped a tableView component on to the viewController.
The cells have been created with a new class HistoryTableViewCell of type UITableViewCell and this class also has an XIB.
I have also created a tableViewCell and its XIB and used the following code in the tableView(_,cellForRowAt:) method:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "historyReuseIdentifier", for: indexPath) as! HistoryTableViewCell
cell.meetingDateLabel.text = historyArray![indexPath.section]
return cell
}
This line of code works fine with a tableViewController when I add the following line in the viewDidLoad() method:
tableView.register(UINib(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier: "reuseIdentifier")
But the tableView.register property cannot be used with a tableView. How do I make use of the reusable cells here?
Lets say your xib is ok, everything works fine and the only thing left is to register the nib.
First declare your table view:
#IBOutlet weak var tableView: UITableView!
In the viewDidLoad() :
tableView.register(UINib(nibName: "HistoryTableViewCell", bundle: nil) , forCellReuseIdentifier: "historyReuseIdentifier")
Also check your tableView's dataSource and delegate.
The answer was actually quite simple. I figured it out after reading a few of the other answers.
All I needed was to replace this line:
tableView.register(UINib(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier: "reuseIdentifier")
with this one:
UITableView.register(UINib(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier: "reuseIdentifier")
Since there is no table view by the name tableView, you need to access the UITableView class.
Do you want to reuse the cells thats ok, to use them you need to register it first..suppose you have multiple tableView in a single ViewController, then you have to register each cell with respective tableView.
just register the tableViewCell with respective tableView and use its reusableIdentifier which you have used while registering the tabelViewcell.
Try to create it directly in cellForRowAt indexPath: like this. Here no need of tableView.register(UINib(nibName: "TableViewCell", ... line.
//And replace names with your names
var cell = tableView.dequeueReusableCell(withIdentifier:"cell") as? TableViewCell
if cell == nil {
cell = Bundle.main.loadNibNamed("TableViewCell",owner: self, options: nil)?.first as? TableViewCell
}
I have problems with my custom cell file in tableview. I managed to get it done using the out comment line shown below, but the performance was really bad when it had 10+ cells.
UsingdequeueReusableCell leads to this error:
'NSInternalInconsistencyException', reason: 'unable to dequeue a cell with identifier DiveNewsShort - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'
which is strange, because I do register the nib in viewDidLoad(). I hope you can help me, I am getting frustrated by this.
class ProfilTableView: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(nibName: "DiveNewsShort", bundle: nil), forCellReuseIdentifier: "DiveNewsShort")
tableView.register(DiveNewsShort.self, forCellReuseIdentifier: "DiveNewsShort")
}
public override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// let cell = Bundle.main.loadNibNamed("DiveNewsShort", owner: self, options: nil)?.first as! DiveNewsShort
// This one works as expected
let cell = tableView.dequeueReusableCell(withIdentifier: "DiveNewsShort", for: indexPath) as! DiveNewsShort
// This one does not
return cell }
Update:
I managed to get rid of the error by adding the register function in the cellForRowAt function, but I don't think that this is a efficient way actually. It should work within the vieDidLoad shouldn't it?
public override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
tableView.register(UINib(nibName: "DiveNewsShort", bundle: nil), forCellReuseIdentifier: "DiveNewsShort")
let cell = tableView.dequeueReusableCell(withIdentifier: "DiveNewsShort", for: indexPath) as! DiveNewsShort
return cell }
You don't need this line:
tableView.register(DiveNewsShort.self, forCellReuseIdentifier: "DiveNewsShort")
You already have registered the nib file one line before.
There are three ways to register cells for reuse/dequeuing:
You are programmatically creating the cells, in which case you register the class in viewDidLoad.
You are using a NIB, in which case you register the NIB in viewDidLoad.
You are using storyboard cell prototypes, in which case you don't have to register anything. The storyboard does all of this for you.
Since you are using NIBs, you should remove the registering of the class and only register the NIB. And you should do this in viewDidLoad. This process is outlined in https://stackoverflow.com/a/28490468/1271826 as well as in Reinier's answer.
Looking at your MCVE, your problem was a result of a more fundamental mistake, where you had a UIViewController trying to use another view controller, which was a UITableViewController, to manage the table. But UITableViewController has its own UITableView and won't use the one that you have an #IBOutlet for, so you were registering the NIB for a table view you weren't seeing. There were a ton of other issues here (e.g. if you really want a view controller within a view controller, you have to do view controller containment calls, etc.), but the simplest solution was to excise this separate UITableViewController from the project and when this was fixed, it works precisely as we described. See https://github.com/robertmryan/Divers for a working version of your MCVE.
You also didn't hook up the outlets for the other two controls in your cell (the switch and slider). Thus, if you changed either of those two controls and then scrolled, the cells are reused and you see the changed UIKit control that was done for some other cell, but was subsequently reused. To fix that, your custom UITableViewCell subclass should have outlets for all controls, and cellForRowAt must set values for all of these outlets. You also need some mechanism for the cell to inform the view controller when the switch and slider have changed and update the model accordingly, so when cellForRowAt was later called for that row, it would know the state of that CellData to set the control appropriately. A common solution for this is to use the protocol-delegate pattern. See the above GitHub repo, which illustrates this pattern, too.
I have build this protocol for help me in this process
protocol CBNibInstanceableCellProtocol {
static func getCellXib() -> UINib?
static func getReuseIdentifier() ->String
}
and in your class you have to implement those methods like here
//example implementation
extension CBUsersAttendanceEmptyCell : CBNibInstanceableCellProtocol
{
static func getCellXib() -> UINib?
{
if Bundle.main.path(forResource: "CBUsersAttendanceEmptyCell", ofType: "nib") != nil
{
return UINib(nibName: "CBUsersAttendanceEmptyCell", bundle: nil)
}
return nil
}
static func getReuseIdentifier() ->String
{
return "CBUsersAttendanceEmptyCell"
}
}
then in your viewDidLoad you must do something like this
//example code
self.collectionView.register(CBUsersAttendanceAvatarCell.getCellXib(), forCellWithReuseIdentifier: CBUsersAttendanceAvatarCell.getReuseIdentifier())
self.collectionView.register(CBUsersAttendanceCountCell.getCellXib(), forCellWithReuseIdentifier: CBUsersAttendanceCountCell.getReuseIdentifier())
self.collectionView.register(CBUsersAttendanceEmptyCell.getCellXib(), forCellWithReuseIdentifier: CBUsersAttendanceEmptyCell.getReuseIdentifier())
in your cellForRow
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CBUsersAttendanceCountCell.getReuseIdentifier(), for: indexPath) as? CBUsersAttendanceCountCell
{
return cell
}
You must have defined the class for you view in your xib, this is very important check this pictures
Hope this helps
I'm working through an exercise which uses tableviews. I noticed within a test during the exercise, they use a method I haven't needed in the past when implementing tableviews from storyboards. The method is:
func register(AnyClass?, forCellReuseIdentifier: String)
After reading the short description of this function in the reference pages. I'm curious to know what does apple mean by term "registers"? I half assume that since we are doing this exercise programmatically at the moment, this function is only needed if you're creating UITableviews programmatically. If this statement is incorrect, please let me know as I'd like to learn more.
Here is the code from the example:
func test_CellForRow_DequesCellFromTableView(){
let mockTableView = MockTableView()
mockTableView.dataSource = sut
mockTableView.delegate = sut
mockTableView.register(ItemCell.self, forCellReuseIdentifier: "ItemCell")
sut?.itemManger?.add(ToDoItem.init(title: "Foo"))
mockTableView.reloadData()
_ = mockTableView.cellForRow(at: IndexPath.init(row: 0, section: 0))
XCTAssertTrue(mockTableView.cellGotDequeed)
}
The DequeueReusable methods are there to check if any reusable cells are left before creating new ones. Hope you have an idea about the working of reusable cells
What happens when the queue is empty? Now we do need to create a cell. We can follow 2 methods to create a cell,
Create cell manually
Create it automatically by registering cell with a valid xib file
METHOD 1
if you do it with manually, you must check cell is empty or not after dequeueReusableCell check. Just like below,
// create a cell for each table view row
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Reuse an old cell if exist else return nil
let cell:UITableViewCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as UITableViewCell!
//check cell is nil if nil you want to allocate it with proper cell
if(cell == nil){
//create cell manually
cell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "CellSubtitle")
}
// do stuff to the cell here
return cell
}
METHOD 2
We could create the cell manually like above which is totally fine. But it would be convenient if the table view would create the cell for us directly.
That way we don't have to load it from a nib or instantiate it.
For registering a cell with a xib or class we use func register(AnyClass?, forCellReuseIdentifier: String) method. Let see an example,
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(MyCell.self, forCellReuseIdentifier: "Cell")
}
// ...
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath:indexPath) as MyCell
// no "if" - the cell is guaranteed to exist
// ... do stuff to the cell here ...
cell.textLabel.text = // ... whatever
// ...
return cell
}
You are "registering" your custom Cell class - ItemCell - for reuse as a cell for your tableview.
See: https://developer.apple.com/reference/uikit/uitableview/1614888-register
"Register" tells XCode that the cell exists. A cell is registered under a "reuse identifier." This is a unique string that corresponds to your TableViewCell, in this case ItemCell.
A cell can also be registered in the Storyboard by filling out the "Identifier" in the cell's attributes inspector.
Inside my cell for row at indexPath, I have been using the following code to do most of my work because that is what I have been taught. I was wondering, is it necessary to always use if let to do this work? Because I never find that I ever fall into the else statement.
When would I need to use if let or just let inside cellForRowAtIndexPath?
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCellWithIdentifier("myCustomCell") as? myCustomCell {
} else {
return myCustomCell()
}
}
UITableView has two dequeue modes:
dequeueReusableCell(withIdentifier:): The table view tries to dequeue a cell. If there are none, it will try to create one using the cell you registered with the reuseIdentifier. If you didn't register a cell, it will return nil giving you the chance to create one yourself.
This is where the else clause in your code would come into effect. Basically never, since presumably you did register a cell class. Most likely in the Storyboard (by setting the identifier in the inspector) but you can also do it in code.
dequeueReusableCell(withIdentifier:for:), note the additional parameter. The table view tries to dequeue a cell: If there are none, it will try to create one using the cell class you registered using the reuseIdentifier. If you didn't register a cell, it will crash.
Solution
If you can guarantee that a) the cell is correctly registered and b) the type of the cell is set correctly, you would generally use the second option. This avoids the exact issue you're having:
let cell = tableView.dequeueReusableCellWithIdentifier("myCustomCell", forIndexPath: indexPath) as! myCustomCell
(However, it is still perfectly fine to use if let even in this case.)
I'm working with nib files for the first time and trying to add a tableview with a tableview cell. I created a nib file of type UIView controller, then I dragged the tableview onto the view, and in the viewcontroller.swift I added all of the necessary delegate, datasource, cellForRowatIndexPath, numberOfRowsinSection, etc, just like normal. But the app crashes on loading. I have looked at several other questions, notably these:
Custom UITableViewCell from nib in Swift
Can't make UiTableView work
But those solutions did not work for me completely and it still crashes on loading. Another error message I've gotten has been "this class is not key value compliant."
So, what are the exact steps to make a uitableview in a nib file? From what I understand:
File--> New-->File-->Cocoa Touch Class-->Subclass UITableViewController. this sets up the table view. we will call this View1.swift
File-->New-->File-->Cocoa Touch Class-->Subclass UITableViewCell. this sets up the cell in the tableview. we'll call this View1TableCell.swift
in View1.swift, register the nib:
tableView.registerNib(UINib(nibName: "View1", bundle: nil), forCellReuseIdentifier: "View1CellID")
Give the cell a reuse identifier. We will say this is "View1CellID"
in View1.swift, in cellforRowAtIndexPath, dequeue the cell with the correct cell identifier.
So, all these steps should work so that I can add any label or button to my View1TableCell nib, and those changes will be seen on the tableview when I build and run, correct? What am I doing wrong?
The nib you register should be the one containing the cell, not the view controller. From Apple docs:
Parameters
A nib object that specifies the nib file to use to
create the cell. This parameter cannot be nil.
So the view that holds your table is View1, from your explanation.
If you want to implement a custom cell, you need to create a new class, to extend UITableViewCell. That class should also have a nib
So create a new class
class MyCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
setupCell()
}
func setupCell() {
//setup your ui here
}
}
Now in View1.swift, in viewDidLoad, register the nib
func viewDidLoad() {
super.viewDidLoad()
//register the nib of the cell for your cell
tableView.registerNib(UINib(nibName: "MyCell", bundle: nil), forCellReuseIdentifier: "MyCellIdentifier")
}
now use the cell in your tableview
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MyCellIdentifier") as! MyCell
//set the stuff you need in your cell
return cell
}