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
}
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'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.
Table view cell in cellForRowAt alway has all properties set to nil
import UIKit
class TodoTableViewCell: UITableViewCell {
#IBOutlet weak var label: UILabel!
}
class TodosViewController: UITableViewController {
#IBOutlet var TodosTableView: UITableView!
var projects = [Project]()
var todos = [Todo]()
override func viewDidLoad() {
super.viewDidLoad()
TodosTableView.delegate = self
self.tableView.register(TodoTableViewCell.self, forCellReuseIdentifier: "TodoTableViewCell1")
// data init
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "TodoTableViewCell1"
var todo = projects[indexPath.section].todos[indexPath.row]
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? TodoTableViewCell else {
fatalError("The dequeued cell is not an instance of MealTableViewCell.")
}
cell.label?.text = todo.text // cell.label is always nil
return cell
}
}
It seems like identical issue
Custom table view cell: IBOutlet label is nil
What I tried to do:
- restart Xcode
- recreate outlet
- clean project
- recreate view cell from scratch like here https://www.ralfebert.de/ios-examples/uikit/uitableviewcontroller/custom-cells/
Please help, iOS development drives me nuts already.
You don't need to register the class in the tableview if you're using prototype cells in Interface Builder. Try removing the registration function from viewDidLoad. Incidentally you can also set dataSource and delegate in IB - much neater code-wise.
You are using the UITableView instance method:
func register(AnyClass?, forCellReuseIdentifier: String)
This only works if your custom UITableViewCell subclass is not setup using Interface Builder
If you've created your subclass using an xib. You should use:
func register(UINib?, forCellReuseIdentifier: String)
like:
let nib = UINib(nibName: "\(TodoTableViewCell.self)", bundle: nil)
self.tableView.register(nib, forCellReuseIdentifier: "TodoTableViewCell1")
If you're using prototype cells in a storyboard you don't need to register your cells at all.
I think the identifier of the cell should be in the identifier from the attributes inspector column not the Identity inspector
and in module in Identity inspector add your project
Important note: One issue I haven't seen discussed is that if you use prototype cells in the storyboard, then explicitly registering the cell will make your outlets nil! If you explicitly register the cell then you are registering it without the storyboard which has your iboutlets. This will mean you defined your outlets in your cell but they aren't connected. Deleting the explicit registration will solve the issue.
Doesn't work:
tableVIew.register(MenuCell.self, forCellReuseIdentifier: "MenuCell")
Works:
// tableVIew.register(MenuCell.self, forCellReuseIdentifier: "MenuCell")
I have created a View Controller having UITableView with custom cell and xib. Added a UITextField in custom cell xib. Now I have connected the delegate of UITexField to the File's Owner of custom cell xib and in the View controller I have added below code :
extension MyViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.endEditing(true)
return true
}
}
Above method is not getting called.
It work's if I add below code in cellForRowAtindexPath :
cell.myTextField.delegate = self
Can someone help me, why connecting the UITextField delegate to File's owner in custom cell not working.
EDIT
I am registering nib like this:
myTableView.register(UINib(nibName: "MyCustomTableViewCell", bundle: nil), forCellReuseIdentifier: "mycustomcellreuseidentifier")
and in cellForRowAtindexPath:
let cell = tableView.dequeueReusableCell(withIdentifier: "mycustomcellreuseidentifier", for: indexPath) as! MyCustomTableViewCell
I haven't added any code in MyCustomTableViewCell except the #IBOutlet for UITextField.
Edit - with the help of #Puneet Sharma and #Francesco Deliro
Tried below code:
let myCustomCellNib = UINib(nibName: "MyCustomTableViewCell", bundle: nil)
myCustomCellNib.instantiate(withOwner: self, options: nil)
myTableView.register(myCustomCellNib, forCellReuseIdentifier: "mycustomcellreuseidentifier")
and as #Puneet Sharma suggested to change the File's Owner inside nib. I did below change in Identity inspector by adding the class name to MyViewController:
Still not working.
Should I go with:
cell.myTextField.delegate = self
in cellForRowAtindexPath.
You have never set the owner of the nib to your MyViewController.
You must be calling registerNib:forCellReuseIdentifier: in your MyViewController class, where you are providing the UINib object.
You must have created this UINib object, by calling init(nibName:bundle:). There, you have not provided the owner of this nib as MyViewController.
About FileOwner, this is from Apple's documentation:
File’s Owner object is a placeholder object that is not created when
the nib file is loaded. Instead, you create this object in your code
and pass it to the nib-loading code.
In case of UIViewController nib/storyboard, FileOwner is already set by XCode template so you can make direct IBOutlet connections.
In case of your custom tableviewCell, the fileowner needs to be provided through code.
This can be done using [instantiate(withOwner:options:)][2] method on nib object (I have shamelessly copied the code from Fransesco's answer above).
let nib = UINib(nibName: "MyCustomTableViewCell", bundle: nil)
nib.instantiate(withOwner: self, options: nil)
myTableView.register(nib, forCellReuseIdentifier: "mycustomcellreuseidentifier")
Earlier, I thought the fileowner can be set using UINib method of instantiate(withOwner:) method, but although this method is for setting the nib owner, it does not work in case of tableview.register(nib:) method.
This is perhaps, because when tableview tries to dequeue the cell for the first time and it does not find one, it creates the cell from the registered nib but set its owner as nil. I have not found the official doc supporting this, but this is what is perhaps happening which is resulting in issues in the sample project.
I have removed the registerNib code from the sample, and created the cell in cellForRowAtIndexPath:, and this worked.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "mycellreuseidentifier") as? MyTableViewCell
if cell == nil {
let array = Bundle.main.loadNibNamed("MyTableViewCell", owner: self, options: nil)
cell = array!.first as! MyTableViewCell
}
return cell!
}
This alone, though is not enough. You have to change the File Owner inside nib as MyViewController. This is to be done in Identity inspector tab for the nib. Just set MyViewController as the FileOwner.
Although, this worked, but I guess if you need to write this much in cellForRowAtIndexPath:, its better you set delegate there itself as you have done in your commented code.
In cell.myTextField.delegate = self self is your UIViewController, that is connected with the delegate MyViewController: UITextFieldDelegate.
If you have made the connection in your cell xib your reference is the cell. So if you want that your connection works there you have to set the connection with the cell MyCell: UITextFieldDelegate.
Edit:
If you want to use your delegate in your ViewController you have to follow Puneet answer in this way:
let nib = UINib(nibName: "MyCustomTableViewCell", bundle: nil)
_ = nib.instantiate(withOwner: self, options: nil)[0]
myTableView.register(nib, forCellReuseIdentifier: "mycustomcellreuseidentifier")
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