I'm trying to populate a UITableView with custom UITableViewCells using FirebaseUI. Everything seems to work except that the TableViewCells delivered by the FirebaseTableViewDataSource.populateCell() method are not "wired" to the outlets in the view. Here's an example:
This is the custom UITableViewCell class. The UITableView in the storyboard for the view controller being loaded has this custom cell class.
class JobTableViewCell : UITableViewCell {
#IBOutlet weak var labelJobName: UILabel!
}
Here's the code in ViewDidLoad() of the view controller that is setting up the table:
dataSource = FirebaseTableViewDataSource(ref : ref, cellClass : JobTableViewCell.self, cellReuseIdentifier: "JobTableViewCell", view: self.tableView);
dataSource.populateCell{(cell : UITableViewCell, obj : NSObject) -> Void in
let snapshot = obj as! FIRDataSnapshot;
let job = Job(snapshot);
let jobCell = cell as! JobTableViewCell
//////////////////////////////////////////////////////////
// jobCell and its outlets are nil so this next statement
// causes exception.
jobCell.labelJobName.text = job.name;
}
self.tableView.dataSource = self.dataSource;
So the question is how to get FirebaseUI to deliver the custom cell with the outlets wired up? I can't figure out how to do it.
My solution was to stop using FirebaseUI for iOS .
You need to use self.tableView.bind
For example,
var dataSource: FUITableViewDataSource!
self.dataSource = self.tableView.bind(to: ref) { tableView, indexPath, snapshot in
// Dequeue cell
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)
/* populate cell */
return cell
}
See Firebase UI database for iOS for more info
Related
Question
I'm creating a re-usable custom UITableViewCell and then subclassing it for re-use. How do I set these various subclasses to link to the same xib file?
Background
Let's call my custom UITableViewCell PickerTableViewCell. This cell includes a UIPickerView, as well as all the implementations as to how the picker view looks and behaves. When I want to use this cell, the only thing I need to give it is the data for the picker view. So I subclass PickerTableViewCell, then simply create the data source I need and assign it to the picker view. So far this has all worked well.
Here are the relevant parts of PickerTableViewCell:
class PickerTableViewCell: UITableViewCell {
var picker: UIPickerView! = UIPickerView()
var pickerDataSource: PickerViewDataSource!
override func awakeFromNib() {
super.awakeFromNib()
self.picker = UIPickerView(frame: CGRect(x: 0, y: 40, width: 0, height: 0))
self.picker.delegate = self
self.assignPickerDataSource()
}
// Must be overriden by child classes
func assignPickerDataSource() {
fatalError("Must Override")
}
Here is an example of a subclass:
class LocationPickerTableViewCell: PickerTableViewCell {
override func assignPickerDataSource() {
self.pickerDataSource = LocationPickerDataSource()
self.picker.dataSource = self.pickerDataSource
}
}
Problem
Since I am using these cells all over the place, with different data sources, I created a xib file which defines how the cell looks called PickerTableViewCell.xib, and assign it to the class PickerTableViewCell. In the view controllers I want to use it for, I register the cell with the table view inside viewDidLoad(). Then, inside func tableView(_:, cellForRowAt) I dequeue the subclass I want like this:
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier") as! LocationPickerTableViewCell
return cell
This is where the problem happens. The cell that is created is a PickerTableViewCell, not its subclass LocationPickerTableViewCell. This runs into the fatal error I placed in the parent class which is overriden by the child class.
The only way I have found to solve this is to create a separate xib file for each subclass I want to create, and assign it to the relevant subclass. While this solution does work, it feels wrong to have all of these xib files which are practically identical (except for which class they are assigned to) inside my project.
Is there a way I can overcome this problem, and have all of these cells link to the same single xib file?
Thanks! :)
Add view loaded by xib to UITableViewCell classes in which you want to use it.
Create your xib as per your require design, in your example PickerTableViewCell.xib
Create UITableViewCell sub-classes in which you want to use that view. I am using FirstTableViewCell & SecondTableViewCell for this.
in constructor of table cell load the xib and add it to table cell.
let nib = Bundle.main.loadNibNamed("PickerTableViewCell", owner: nil, options: nil)
if let view = nib?.first as? UIView{
self.addSubview(view)
}
if xib have any #IBOutlet then get them by viewWithTag function and assign to class variables
if let label = self.viewWithTag(1) as? UILabel{
self.label = label
}
override reuseIdentifier var of each tableviewCell subclass with different name
override var reuseIdentifier: String?{
return "FirstTableViewCell"
}
Now You can use these classes where you want, for using this follow below steps:
register this tableviewCell subclass with xib with tableview:
tableView.register(FirstTableViewCell.self, forCellReuseIdentifier:"PickerTableViewCell")
now in cellForRowAt indexPath method use it.
var cell = tableView.dequeueReusableCell(withIdentifier: "FirstTableViewCell", for: indexPath) as? FirstTableViewCell
if cell == nil {
cell = FirstTableViewCell()
}
cell?.label?.text = "FirstTableViewCell"
Don't use subclassing to assign different data sources.
Approach 1: Assign pickerDataSource in tableView(_:cellForRowAt:)
In the table view controller, you need to assign pickerDataSource
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier") as! PickerTableViewCell
cell.pickerDataSource = LocationPickerDataSource()
return cell
}
Handle additional work needed after the assignment of pickerDataSource with a didSet.
class PickerTableViewCell: UITableViewCell {
var picker: UIPickerView! = UIPickerView()
var pickerDataSource: PickerViewDataSource! {
didSet {
self.picker.dataSource = self.pickerDataSource
}
}
…
}
Approach 2: Extend PickerTableViewCell in all the needed ways.
Here instead of subclassing add the needed logic to a uniquely named setup method each defined in their own extension.
extension PickerTableViewCell {
func setupLocationPickerDataSource() {
self.pickerDataSource = LocationPickerDataSource()
self.picker.dataSource = self.pickerDataSource
}
}
then in tableView(_:cellForRowAt:)
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier") as! PickerTableViewCell
cell.setupLocationPickerDataSource()
return cell
}
I am trying to register UITableViewCell in viewdidload
self.tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: "CustomTableViewCell")
In cellForRowAtIndex
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomTableViewCell") as! CustomTableViewCell
cell.productNameLabel.text = "Product"
cell.productNameLabel.textColor = UIColor.darkGray
return cell
}
Here it is crashing in cell.productNameLabel.text.
What is the purpose of registering cell? why it is crashing?
I want to reload data even if cell or table is not visible.
Crashreport :
See the Apple's comments which answers your query on the purpose of registering cell :
Prior to dequeueing any cells, call this method or the
register(_:forCellReuseIdentifier:) method to tell the table view how
to create new cells. If a cell of the specified type is not currently
in a reuse queue, the table view uses the provided information to
create a new cell object automatically.
This is the standard procedure I apply while working with Custom Cells (if you are using xib) :
Set cell's identifier in Xib's attribute inspector :
Register Xib :
self.tableTasks.register(UINib(nibName: "TaskCell", bundle: nil), forCellReuseIdentifier: "taskCell")
However, if you are not using Xib and creating custom cell using code only, then use registeCell :
self.tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: "CustomTableViewCell")
Are you using a xib for this cell? If so, none of the outlets will be connected if you just register the class of the cell. You need to register the actual xib file, so that everything can be connected correctly when the cell is created. Have a look at
-(void)registerNib:(UINib *)nib forCellReuseIdentifier:(NSString *)identifier
https://developer.apple.com/documentation/uikit/uitableview/1614937-registernib
My method for register cell.
Syntax sugar
protocol BSCellProtocol {
// For `registerCell`
static var NibName: String! { get }
// For `registerCell`, `dequeueCellWithType`, and `dequeueHeaderFooterWithType`
static var Identifier: String! { get }
}
extension UITableView {
func registerCell(_ type: BSCellProtocol.Type) {
let nib = UINib(nibName: type.NibName, bundle: nil)
let identifier = type.Identifier!
self.register(nib, forCellReuseIdentifier: identifier)
}
func dequeueCellWithType<T: BSCellProtocol>(_ type: T.Type) -> T {
let cell = self.dequeueReusableCell(withIdentifier: type.Identifier) as! T
return cell
}
func dequeueCellWithType<T: BSCellProtocol>(_ type: T.Type, index: IndexPath) -> T {
let cell = self.dequeueReusableCell(withIdentifier: type.Identifier, for: index) as! T
return cell
}
}
Usage
class MyCustomCell: UITableViewCell, BSCellProtocol {
static var NibName: String! = "MyCustomCell"
static var Identifier: String! = "cellIdentifier_at_Xib"
#IBOutlet weak var lblTitle: UILabel!
// other IBOutlet components
}
// In ViewController, register cell
tableView.registerCell(MyCustomCell.self)
// dequeue cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// cell is `MyCustomCell` instance
let cell = tableView.dequeueCellWithType(MyCustomCell.self)
// configure cell ...
// ....
return cell
}
I had the same problem. I also was not using XIB for cell. My view was not connected to View in File's Owner Outlets. Maybe this info will help someone.
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 recently tried changing my UITableViewController to a UITableView within a UIView. I changed back to this as I was experiencing an error with my UISearchBar, as when I would tap a key to search my app would crash with the error:
fatal error: unexpectedly found nil while unwrapping an Optional value
on this line:
var cell = tableView.dequeueReusableCellWithIdentifier("rideCell") as! RideCell
When I switched back to the UITableViewController this error went away and everything was fine, however I've just tested it again and it is again giving me that error.
Anyone have any suggestions? It works fine for the normal table view, it's just when I go to do a search that it crashes. The identifier is definitely correct.
Thanks!
EDIT:
Full function:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("rideCell", forIndexPath: indexPath) as! RideCell
var ride: Ride
if tableView == self.searchDisplayController?.searchResultsTableView {
ride = DataManager.sharedInstance.getRideByName(searchResults[indexPath.row].name)!
} else {
ride = DataManager.sharedInstance.rideAtLocation(indexPath.row)!
}
cell.rideNameLabel.text = ride.name
var dateSinceUpdate = NSDate().timeIntervalSinceDate(ride.updated!)
var secondsSinceUpdate = Int(dateSinceUpdate)
var timeSinceUpdate = printSecondsConvert(secondsSinceUpdate)
cell.updatedLabel.text = timeSinceUpdate
if ride.waitTime == "Closed" {
cell.waitTimeLabel.text = ride.waitTime!
cell.timeBackgroundView.backgroundColor = getColorFromNumber(80)
cell.waitTimeLabel.font = UIFont(name: "Avenir", size: 13)
} else {
cell.waitTimeLabel.text = "\(ride.waitTime!)m"
cell.timeBackgroundView.backgroundColor = getColorFromNumber(ride.waitTime!.toInt()!)
cell.waitTimeLabel.font = UIFont(name: "Avenir", size: 17)
}
AsyncImageLoader.sharedLoader().cancelLoadingURL(cell.rideImageView.imageURL)
cell.rideImageView.image = UIImage(named: "Unloaded")
cell.rideImageView.imageURL = NSURL(string: ride.rideImageSmall!)
return cell
}
Discovered an extremely simple solution to the issue. Had to change this:
var cell = tableView.dequeueReusableCellWithIdentifier("rideCell", forIndexPath: indexPath) as! RideCell
to this:
var cell = self.tableView.dequeueReusableCellWithIdentifier("rideCell", forIndexPath: indexPath) as! RideCell
There are a few possibilities that you are seeing a fatal error of nil message in your dialog.
Possibility #1: Make sure you have a subclass of UITableViewCell named RideTableViewCell.swift.
To create a subclass of UITableViewCell simply follow the procedures below.
Right-Click on your Project name and create New File... in
Project Navigator
From iOS->Source create Cocoa Touch Class
In Option Dialog Subclass Field Type UITableViewCell
I believe you have an Custom XIB file already, if not, Check Also create XIB file
Make sure you input your XIB identifier in Attribute Inspector
Register your cell class in viewDidLoad() function like so:
let nibCell = UINib(nibName: "RideTableViewCell", bundle: nil)
self.tableView.registerNib(nibPosts, forCellReuseIdentifier: "RideCell")
Register your custom cell in cellForRowAtIndexPath like so:
let cell: RideTableViewCell = self.tableView.dequeueReusableCellWithIdentifier("RideCell", forIndexPath: indexPath) as! RideTableViewCell
Possibility #2: Maybe when you create your custom XIB you didn't tell it which class it belongs to. To set the class of XIB, follow the procedures below.
Click on your .xib file in Project Navigator
Go to identity inspector of your cell and make sure RideTableViewCell is in there.
Please comment if you have any question. Cheers!
Please check did you have given proper class name (RideCell), filled proper module(Your target) and finally the identifier in the storyboard. If this is ok please share SS of your storyboard tableView cell.
and
Hope it helps
If you are not using UITableViewController, then check the following extension are added or not.
class XYZViewController: UIViewController, UITableViewDataSource, UITableViewDelegate,UISearchBarDelegate, UISearchDisplayDelegate, UISearchResultsUpdating
{
or Check this Tutorial. It might help you to solve this error.
Please check that your delegates have been properly set in viewDidLoad and that you are inheriting delegate methods of UITableView and search functions like so:
class YourClass: UIViewController, UITableViewDelegate, UITableViewDataSource {
func viewDidLoad() {
tableView.delegate = self
tableView.dataSource = self
}
}
and do the same for the search bar delegates and data sources. More on that here.
It is showing nil because there is no UItableViewCell which is of type RideCell. You have to create a new RideCell.swift which will be a subclass of UITableViewCell and then associate that with the cell of your tableView and then proceed .
Make sure you fill the correct parameters in the code below.
private let cellReuseIdentifier = "MyCell"
tableView.registerNib(UINib(nibName: "MyCell", bundle: nil), forCellReuseIdentifier: cellReuseIdentifier)
I have made a ViewController, in that ViewController, I have made a TableView , in the TableView I have added a TableViewCell.
In the TableViewCell, I have added a text label and an image.
Iv'e made a cocoa touch file named customCell and connected it with the TableView Cell.
In the ViewController file I wrote this:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:customCell = tableView.dequeueReusableCellWithIdentifier("cell") as! customCell
cell.imageCountry.image = UIImage(named: "2xd.png")
cell.nameCountry.text = "hey"
return cell
This is what i wrote in the customCell file:
import UIKit
class customCell: UITableViewCell {
#IBOutlet var imageCountry: UIImageView!
#IBOutlet var nameCountry: UILabel!
}
Iv'e connected the TableView & the ViewController with the DataSource & Delegate.
When I run my code this is the error I get:
Could not cast value of type 'UITableViewCell' (0x10d1119f0) to 'refreshing.customCell' (0x10b2f6dd0).
*refreshing is the name of the project.
And the green line of the bugger is set to this line:
let cell:customCell = tableView.dequeueReusableCellWithIdentifier("cell") as! customCell
Any suggestions?
you are using the old decking method, that either rquires you to create the cell or use the new that takes the indexPath
let cell:customCell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath:indexPath) as! customCell
if it is still failing, you need to register the cell, either by setting it up in the storyboard or by registering either a class or a nib file in code.
override func viewDidLoad() {
super.viewDidLoad()
tableView.registerNib(UINib(nibName: "customCell", bundle: nil), forCellReuseIdentifier: "cell")
}
or
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.registerClass(customCell.self, forCellReuseIdentifier: "cell")
}
You need to set your custom class as the cell's class here ^
And to subclass, ensure in your CustomClass.h file you have
#interface CustomClass : UITableViewCell