I have a UITableViewController in which I use more than one custom cell. I have used multiple custom cells in the past with no problem, and I am using the same strategy to implement them as I have in the past so this error baffles me. First, some code:
This is how I register the custom cells in viewDidLoad:
// Registering the custom cell
let nib1 = UINib(nibName: "OmniCell", bundle: nil)
tableView.registerNib(nib1, forCellReuseIdentifier: "omniCell")
let nib2 = UINib(nibName: "RankCell", bundle: nil)
tableView.registerNib(nib2, forCellReuseIdentifier: "rankCell")
This is how I create them in cellForRowAtIndexPath:
let cell1: OmniCell = self.tableView.dequeueReusableCellWithIdentifier("omniCell") as! OmniCell
let cell2: RankCell = self.tableView.dequeueReusableCellWithIdentifier("rankCell") as! RankCell
when I comment out the code related to the second custom cell the program runs fine, but when both custom cells are used I get this error:
this class is not key value coding-compliant for the key rankCell.
Both custom cells are implemented in an identical manner so I am not sure why the second custom cell generates this error while the first one does not. What am I missing?
Update: Upon request I am sharing the entire cellforrowatindexpath func:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// This line works:
let cell1: OmniCell = self.tableView.dequeueReusableCellWithIdentifier("omniCell") as! OmniCell
// It is this line that breaks if not commented out:
let cell2: RankCell = self.tableView.dequeueReusableCellWithIdentifier("rankCell") as! RankCell
// This line works:
let cell3: ProgressCell = self.tableView.dequeueReusableCellWithIdentifier("progressCell") as! ProgressCell
if indexPath.row == 0 {
return cell1
} else if indexPath.row == 1 {
return cell2
} else {
return cell3
}
}
I managed to resolve this. The problem was that there was an errant connection in the XIB. Refer to this picture:
There was an additional "broken" outlet above the one good one that was like:
rankCell ---> Rank Cell
But instead of a filled in circle it had what appeared to be an elongated symbol that was too small to make out. I removed the outlet and the app built and ran normally. I am not sure where that connection came from, none of the other custom cells had one like it.
Related
I have a tableview with two custom nib cells registered. The cells and their corresponding connected classes are completely independent of each other.
The tableview based on a condition should present either of one of the two cells registered:
The tableview code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//Show only the table cells that have content.
tableView.tableFooterView = UIView(frame: CGRect.zero)
let rowData = FriendsTab.potentialFriendList[indexPath.row]
if (rowData.pendingInvite){
let cell = potentialFriendTableView.dequeueReusableCell(
withIdentifier: requestCellIdentifier, for: indexPath)
as! FriendRequestCell
cell.activityIndicator.isHidden = true
cell.activityIndicator.hidesWhenStopped = true
cell.userName.text = firstName + " " + lastName
return cell
}
else{
let cell = potentialFriendTableView.dequeueReusableCell(
withIdentifier: potentialCellIdentifier, for: indexPath)
as! PotentialFriendCell
cell.activityIndicator.isHidden = true
cell.activityIndicator.hidesWhenStopped = true
cell.userName.text = firstName + " " + lastName
return cell
}
}
The cell registration code (from viewDidLoad):
let potentialFriendXib = UINib(nibName: self.potentialCellIdentifier, bundle: nil)
let requestFriendXib = UINib(nibName: self.requestCellIdentifier, bundle: nil)
self.potentialFriendTableView.register(potentialFriendXib,forCellReuseIdentifier: self.potentialCellIdentifier)
self.potentialFriendTableView.register(requestFriendXib,forCellReuseIdentifier: self.requestCellIdentifier)
For some reason the FriendRequestCell calls awakeFromNib() twice however the PotentialFriendCell works as expected (one single call).
I have searched the few (very old) SO questions on this but they seem to deal with nested (parent child) nibs which this project does not use (they are individual).
Any ideas where I've gone wrong?
All of this is done in InterfaceBuilder. In interface builder, open up your storyboard or xib that contains the tableviewcontroller. Then select to use Content: Dynamic Prototypes, the select 2 for Prototype Cells. You will then be given 2 TableView Cells. The next step is to do exactly what you did for the cells in each xib. Now there is no loading of nib, and no registering. You still have to do the dequeueReusableCell for each cell.
Basically I need to do what is done here - Not able to add static UITableViewCell into tableview
But I need to do it in swift and I do not know how to convert the objective C solution into swift
Image of the solution I need in swift
First this is not the place to ask for code sample. You should have a look to solutions like Codementor where you can find exactly the kind of help you need ;)
They are different approaches to your problem.
First, you are using a xib, which makes it impossible to add cells in it.
Instead you couldd use a storyboard. In that case you'll be able to do whatever you want and add custom cell.
1 - If you really have to use xib, then you can load custom cells by using alanlo approach. I would suggest to use an extension to register your custom cell like so:
extension UITableViewCell {
class func register(tableView: UITableView) {
let identifier = String(describing: self)
let nib = UINib(nibName: identifier, bundle: nil)
tableView.register(nib, forCellReuseIdentifier: identifier)
}
}
Note that your cell identifier will need to have the same name as your custom tableviewcell class (here it would be YourCustomCellTableViewCell)
2- Then in viewdidload:
YourCustomCellTableViewCell.register(tableView: yourTableView)
3 - Then thanks to the fallowing extension you can easily dequeue your cell:
extension UITableView {
func dequeue<T: UITableViewCell>(indexPath: IndexPath) -> T {
let identifier = String(describing: T.self)
guard let cell = dequeueReusableCell(withIdentifier: identifier, for: indexPath) as? T else {
fatalError()
}
return cell
}
}
4 - dequeue it in the cellForRowAtusing:
let cell: YourCustomTableViewCell = yourTableView.dequeue(indexPath: indexPath)
return cell
Try:
let nibName = "nameOfYourCellNib"
let cell = Bundle.main.loadNibNamed(nibName, owner: nil, options: nil)?.first as? {YOUR_CELL_CLASS_HERE}
May I know whether you are trying to create static cells or dynamic cells?
The solution can be simply changing the cell in the table view instead of adding it in codes or separate xib
In my App I use UITableView with custom cells.
for each cell I implement function to create it, and call these functions in cellForRow.
this is an code example from project :
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row == CellsTypes.profileImageCell.rawValue {
return createIntroCell()
}
else if indexPath.row == CellsTypes.fullNameCell.rawValue {
return createUserNameCell()
}
else if indexPath.row == CellsTypes.emailCell.rawValue {
return createEmailCell()
}
else {
return createPasswordCell()
}}
and these are the creation functions :
func createUserNameCell() -> TextFieldTableViewCell {
let fullNameCell = self.tableView.dequeueReusableCellWithIdentifier("text-field-cell") as! TextFieldTableViewCell
fullNameCell.awamirTextFieldNib.setNormalTextFieldCellContent("Full Name")
fullNameCell.awamirTextFieldNib.textFieldContent.tag = CellsTypes.fullNameCell.rawValue
fullNameCell.awamirTextFieldNib.textFieldContent.returnKeyType = .Next
fullNameCell.awamirTextFieldNib.textFieldContent.delegate = self
return fullNameCell
}
func createEmailCell() -> TextFieldTableViewCell {
let emailCell = self.tableView.dequeueReusableCellWithIdentifier("text-field-cell") as! TextFieldTableViewCell
emailCell.awamirTextFieldNib.setNormalTextFieldCellContent("Email")
emailCell.awamirTextFieldNib.textFieldContent.tag = CellsTypes.emailCell.rawValue
emailCell.awamirTextFieldNib.textFieldContent.returnKeyType = .Next
emailCell.awamirTextFieldNib.textFieldContent.delegate = self
return emailCell
}
func createPasswordCell() -> TextFieldTableViewCell {
let textFieldCell = self.tableView.dequeueReusableCellWithIdentifier("text-field-cell") as! TextFieldTableViewCell
textFieldCell.awamirTextFieldNib.setPasswordCellContent("Password")
textFieldCell.awamirTextFieldNib.textFieldContent.tag = CellsTypes.passwordCell.rawValue
textFieldCell.awamirTextFieldNib.textFieldContent.returnKeyType = .Next
textFieldCell.awamirTextFieldNib.textFieldContent.delegate = self
return textFieldCell
}
the problem is if I reload the tableview the content of cells changed because of the reusablity of the cells. i.e: after reload the tableview the content of the first cell become in the second cell, and the content of the sencond on became in the first cell!!
how can I prevent tableview from reusable the cells?!
Thanks.
Try using a different identifier for each of the cell types, so dont use "text-field-cell" for each one, make one "full name", "password" etc. not sure how you are going about creating your cells, but if you are using the registerNib or registerClass, you will need to register it for each different identifier
self.tableView.registerNib(UINib(nibName: "CustomCell", bundle: nil), forCellReuseIdentifier: "full name")
self.tableView.registerNib(UINib(nibName: "CustomCell", bundle: nil), forCellReuseIdentifier: "password")
self.tableView.registerNib(UINib(nibName: "CustomCell", bundle: nil), forCellReuseIdentifier: "email")
Try to implement prepareForReuse method in your custom cell and set all fields text to nil.
override func prepareForReuse() -> Void {
awamirTextFieldNib.text = nil
}
Hope this help
Don't use the default UITableView and fight the reuse of cells, it's really embedded in it's behaviour.
Try to adapt your code so it works well with reusing cells, or if that's really impossible, you'd have to write your own custom table view I guess (but I don't recommend that at all)
You shouldn't prevent that, but if you really want to, I think that not setting cell identifier to your UITableViewCell should do the trick. When you'll call dequeueReusableCellWithIdentifier, it will find no cell and will create a new one. But again, it's not recommended as UITableView is made to be used with reuse ability.
Moreover, as #Fonix suggests, using a different cell identifier for each cell of your UITableView might solve your issue while keeping using cell reuse system.
Edit :
As you're talking about losing text content in your UITextField, maybe the single change to make is changing your #propery weak attribute to strong.
Hope this helps.
I am using a UITableView in one of my view controllers and am using a custom class called ListCell. The table view and the cells are being displayed, but the content in the cells are not being displayed, so the cell is empty. But if I press the cell it performs its task. So the content is there but its not being displayed. I think it has something to do with how I registered the class to the custom type ListCell. Here is how I declared it in viewDidLoad...
self.tblListView.registerClass(ListCell.self, forCellReuseIdentifier: "Cell")
Is there something wrong with the above line. And what it is causing the cell content to not being displayed?
Also I tried enabling size classes and that did not work. Also all the cells are being created programmatically.
The below line of code is responsible for displaying the cell data.
cell.displayAlertData(mutArrAlertList.objectAtIndex(indexPath.row) as! [NSObject : AnyObject])
the thing is mutArrAlertList's object at the first index is an optional type Optional(Temperature). I think the only way this will work if it is not optional. The object is being passed my a push notification, and what the push notification sends is not optional. So somewhere it randomly turns into an optional type. Is there a way I can get rid of that optional value around it?
Here is cell for row method:
func tableView (tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier: String = "Cell"
var cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! ListCell
//if cell == nil
//{
//let nib: NSArray = NSBundle.mainBundle().loadNibNamed("ListCell", owner: self, options: nil)
//cell = (nib.objectAtIndex(0) as? ListCell)!
//}
cell.selectionStyle = UITableViewCellSelectionStyle.None
print("")
print("This is cell: \(mutArrAlertList.objectAtIndex(indexPath.row) as! [NSObject : AnyObject])") //did this to check what the value is and it is an optional
cell.displayAlertData(mutArrAlertList.objectAtIndex(indexPath.row) as! [NSObject : AnyObject])
if indexPath.row == mutArrAlertList.count-1
{
if ApplicationDelegate.intPage > mutArrAlertList.count
{
//do nothing
}
else
{
ApplicationDelegate.intPage = ApplicationDelegate.intPage + 10
self.reloadDataFromdb()
}
}
return cell
}
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)