I am trying to add a custom cell to tableview in Swift3 but getting a strange error. Here is the screenshot of error.
Write indexPath in place of IndexPath like this:
let cell = tableview.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CustomCell
Also, make sure that CustomCell is subclass of UITableViewCell.
Replace the placeholder IndexPath with lowercase indexPath
Placeholders – with a light blue background – are tokens which indicate the expected types of the parameters.
But you got more serious issues than that error.
Your CustomCell class does not inherit from UITableViewCell. Change it to:
class CustomCell:UITableViewCell {
// your custom cell implementation
}
Related
I wonder if anyone can offer any guidance? I am writing an iPhone app, using Xcode 13.2.1. I am displaying a tableview within a scene that uses XIBs. It works fine. Above the table I have a header that is being displayed, it too works fine.
However, what I'd like to do is display the header, then display a cell that doesn't use the XIB (and that is a height of 50), and then displays every other cell after that first cell using a XIB (height is 195 - just an FYI). Thus, to do/implement this what I am trying to do is implement some kind of 'if statement' such that if indexPath.row is 0 then set the cell type to <call it cell type 1>, and if the indexPath.row is not 0 then set the cell type to <call it cell type 2>. I don't believe that I can use an IF statement because later in the code block it won't recognise the value of cell because it would have been set in an IF statement. Hence, I think I need to use a turnery operator, however I am struggling to construct the turnery operator.
The current code that sets up the cell for XIB in the
// MARK: TableView CELL Information
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Current code that sets up the cell for a XIB template
guard let cell: CustomTableViewCellTypeA = self.tableView.dequeueReusableCell(withIdentifier: "customCell") as? CustomTableViewCellTypeA else {
os_log("Dequeued cell isn't an instance of CustomTableViewCellTypeA", log: .default, type: .debug)
fatalError()
}
I KNOW THE FOLLOWING CODE DOESN'T WORK - however I am showing it this way to try and explain what I am trying to achieve:
// Intent is to use an IF or turnery operator to set the correct cell type
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
} else {
// Current code that sets up the cell for a XIB template
guard let cell: CustomTableViewCellTypeA = self.tableView.dequeueReusableCell(withIdentifier: "customCell") as? CustomTableViewCellTypeA else {
os_log("Dequeued cell isn't an instance of CustomTableViewCellTypeA", log: .default, type: .debug)
fatalError()
}
}
The follow on code (if I can somehow get the above to work) would mean that I would display some information in the first cell which would be <call it cell type 1> and then display other information in <call it cell type 2>.
Anyone done this before or would have any guidance on how to create such a turnery operator? I have tried many things but can't seem to manage to find the solution.
Cheers James.
Actually this was much easier to solve than trying to add complexity of turnery operators to determine which cell to dequeue. It was simply a case of using an IF and adding in the code I wanted to execute along with ensuring I put a return cell statement in it, meaning that if the IF-statement wasn't executed then the code executes the other dequeue statement... Thus, the code looks like this:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row < 1 {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "Select/tap on an event record below to edit the details of the journey."
cell.textLabel?.textAlignment = .center
cell.textLabel?.numberOfLines = 0
cell.backgroundColor = .orange
return cell
}
guard let cell: CustomTableViewCellTypeA = self.tableView.dequeueReusableCell(withIdentifier: "customCell") as? CustomTableViewCellTypeA else {
os_log("Dequeued cell isn't an instance of CustomTableViewCellTypeA", log: .default, type: .debug)
fatalError()
}
This gave me the result I needed. Also, the feedback from Shawn Frank above helped me realise I hadn't registered the first cell type (only the second one), thus when I registered both the first and second cell types within the class it all worked beautifully. Thank you to all who looked at the question and the fine folks above who gave guidance. Cheers James.
I'm trying to refine some working but ugly code.
My app has five TableViews, each one displaying a different type of data (with different cell layouts). Because the datatypes are similar-ish and require many similar methods (for downloading, encoding, etc), I have set up a TableViewController:UITableViewController class to serve as a superclass for the five TableViewController subclasses. Within this superclass, I have the standard "cellForRowAt" method, but it's bloated and repetitive. I want to simplify it.
My problem (I think) is the multiple "let cell = " statements, which all cast as a different type of TableViewCell depending on the datatypes. For example, my DataType.SCHEDULES datatype needs to get a SchedulesTableViewCell with reuseID of "SchedulesCell". I can't make them all the same TableViewCell class, because they each have their own IBOutlet views.
Making things uglier, each tableView has two cell prototypes, and I need to be able to generate an ARTICLE cell and a DETAIL cell for each datatype.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// get the article and row type
let article = getArticleFor(indexPath: indexPath)
let cellType = getCellTypeFor(indexPath: indexPath)
// create either an ARTICLE row or a DETAIL row.
// (simplified for SO posting. Each "case" is actually
// 5-6 lines of nearly identical code)
switch cellType {
// for the ARTICLE cell prototype
case CellType.ARTICLE:
// get the right table cell matching the datatype
switch self.datatype {
case DataType.SCHEDULES:
let cell = tableView.dequeueReusableCell(withIdentifier: "SchedulesCell") as! SchedulesTableViewCell
cell.fillCellWith(article: article)
cell.otherMethod2()
cell.otherMethod3()
return cell
case DataType.LUNCH:
let cell = tableView.dequeueReusableCell(withIdentifier: "LunchCell") as! LunchTableViewCell
cell.fillCellWith(article: article)
cell.otherMethod2()
cell.otherMethod3()
return cell
case DataType.EVENTS:
let cell = tableView.dequeueReusableCell(withIdentifier: "EventsCell") as! EventsTableViewCell
cell.fillCellWith(article: article)
cell.otherMethod2()
cell.otherMethod3()
return cell
case DataType.DAILY_ANN:
let cell = tableView.dequeueReusableCell(withIdentifier: "DailyannCell") as! DailyannTableViewCell
cell.fillCellWith(article: article)
cell.otherMethod2()
cell.otherMethod3()
return cell
case DataType.NEWS:
let cell = tableView.dequeueReusableCell(withIdentifier: "NewsCell") as! NewsTableViewCell
cell.fillCellWith(article: article)
cell.otherMethod2()
cell.otherMethod3()
return cell
}
// or for the DETAIL cell prototype
case CellType.DETAIL:
// get the right table cell matching the datatype
switch self.datatype {
case DataType.SCHEDULES:
let cell = tableView.dequeueReusableCell(withIdentifier: "SchedulesDetailsCell") as! ScheduleDetailTableViewCell
cell.fillCellWith(article: article)
cell.otherMethod2()
cell.otherMethod3()
return cell
case DataType.LUNCH:
let cell = tableView.dequeueReusableCell(withIdentifier: "LunchDetailsCell") as! LunchDetailsTableViewCell
cell.fillCellWith(article: article)
cell.otherMethod2()
cell.otherMethod3()
return cell
case DataType.EVENTS:
let cell = tableView.dequeueReusableCell(withIdentifier: "EventsDetailsCell") as! EventsDetailTableViewCell
cell.fillCellWith(article: article)
cell.otherMethod2()
cell.otherMethod3()
return cell
case DataType.DAILY_ANN:
let cell = tableView.dequeueReusableCell(withIdentifier: "DailyannDetailCell") as! DailyannDetailsTableViewCell
cell.fillCellWith(article: article)
cell.otherMethod2()
cell.otherMethod3()
return cell
case DataType.NEWS:
let cell = tableView.dequeueReusableCell(withIdentifier: "NewsDetailCell") as! NewsDetailTableViewCell
cell.fillCellWith(article: article)
cell.otherMethod2()
cell.otherMethod3()
return cell
}
}
}
I originally had each "let cell =" case within the subclasses' own "cellForRowAt" methods, but I was repeating very similar code in every subclass, which seemed silly. On the other hand, the code above moved the repetition into a single class, but didn't remove the repetition, so it's still silly, but in a different place.
I feel like if I could make a dictionary of classes, something like this...
let tableCellClasses = [DataType.SCHEDULES : ScheduleTableViewCell,
DataType.LUNCH : LunchTableViewCell
etc.
...then I could make my "let cell = " statements more generic, like...
let cell = tableView.dequeueReusableCell(withIdentifier: identifier[dataType]) as! tableCellClasses[dataType]
but can't seem to find a way to make it work.
As I said, it works but it's ugly. I work in a high school, so I'd like for students viewing the repo to see clean, well-structured code -- so I'm shooting for better than just "it works."
Any suggestions?
You could use Swift's meta types and what not, but looking at your code, all your cell subclasses share the same methods:
cell.fillCellWith(article: article)
cell.otherMethod2()
cell.otherMethod3()
Why not:
Have a base class from which all custom cell classes inherit, that implements the above interface (the three methods you use after dequeuing a cell, with the possibility of them being overriden on each concrete subclass), so dequeue once and force-cast into the base type (I believe the right implementation of the methods will be executed, for each subclass. The cast is only to make the compiler happy: UITableViewCell does not have those methods).
Have a switch clause on the data type that gives you the specific cell identifier
Have each prototype cell set to the specific class on the storyobard, and assign the specific identifier too.
Does it make sense?
Now, form looking at your code, it doesn't look like you really need different subclasses. It's perfectly okay to have several different protoypes of the same UITableViewCell subclass, each with a different subview layout and a different reuse identifier, as long as they all can work with the same number and type of subviews and other custom properties/methods.
There is no way to do what you assume. You have to casting the classes one by one for your need. Or you can use base class which implemented all methods you need and calling them by the datatype.
I got stuck with simple issue and can't find any solution.
MyCell.xib has fileowner MyCell : UITableViewCell class.
I use it like that:
viewDidLoad method:
let nib = UINib(nibName: "MyCell", bundle: nil)
self.tableView.register(nib, forCellReuseIdentifier: "myIdentifier")
tableView cellForRowAt method:
let cell = tableView.dequeueReusableCell(withIdentifier: "myIdentifier", for: indexPath) as? MyCell
It works good.
I subclass my class to add some new methods:
class SuperCell : MyCell {
func coolMethod {
print("cool")
}
}
And try to use it like that:
let cell = tableView.dequeueReusableCell(withIdentifier: "myIdentifier", for: indexPath) as? SuperCell
it returns nil
How can I make it work?
I tried to create prototype cell in InterfaceBuilder with identifier myIdentifier and with class SuperCell, but it didn't help.
Why do I need it
I just want to use the same view (xib) for different cell classes.
In my case I have common cell (MyCell) with view (xib). MyCell completely describes fields (IBOutlets). Also I want to create some another cell classes that subclasses MyCell, but they will provide some behaviour of these IBOutlets. For example FirstMyCell : MyCell will have method setFieldsFrom(objectOne: ObjectOne) and SecondMyCell : MyCell will have another method setFieldsFrom(anotherObject: ObjectAnother).
Of course, I can just add this two methods into my MyCell class, but it will be unclean.
Do not set the files owner (remove it)
Make sure your your XIBs Custom Class is set to SuperCell:
I am converting an app from objective-c to swift. I did not write the objective c code. One of the functions which code is as below...
func tableView (tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier: String = "Cell"
var cell: ListCell? = tableView.dequeueReusableCellWithIdentifier(cellIdentifier)!
And theres more code in the function but i don't think it is relevant. When I run the app, I get an error: "nil found while unwrapping an optional" at the line where I create the variable cell. The error is occurring because I am trying to convert a UITableViewCell to ListCell type. And the conversion is occurring and returning nil. I need to have this line in the code because it is there in the objective-c version of the app.
Is there any way I can get around this and convert a UITableViewCell to ListCell without the conversion failing and returning nil?
Here is the declaration of the ListCell class...
#interface ListCell : UITableViewCell {
//code
}
I did not convert the ListCell class to swift, and instead use a bridging header. Also ListCell is a subclass of UITableViewCell, so why is the conversion not working.
There are two ways you can use dequeueReusableCellWithIdentifier - The most common "modern" way is to have registered the reuse identifier and specified a custom class for your cell in your storyboard. You can also use the UITableView method registerClass:forCellReuseIdentifier to do the same thing programatically.
tableview.registerClass(ListCell.self, forCellReuseIdentifier:"Cell")
In this case you would say
var cell=tableview.dequeueReusableCellWithIdentifier(cellIdentifier) as! ListCell
You can use a forced downcast because you know that the dequeue operation will return a cell of the appropriate type (and if not then you have a problem and throwing an exception is good so you can debug it).
The "old" way was not to register a class and deal with the fact that dequeueReusableCellWithIdentifier may return nil - In this case you need to allocate a new cell yourself;
var cell:Listcell
if let reuseCell = tableview.dequeueReusableCellWithIdentifier(cellIdentifier) as? ListCell {
cell=reuseCell
} else {
cell=ListCell(style: .Default, reuseIdentifier: cellIdentifier)
}
// Now you can configure cell...
Try this, it should work fine.
func tableView (tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cellIdentifier: String = "Cell"
var cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as! ListCell
You should use this version:
let cell = tableView.dequeueReusableCellWithIdentifier("Cell",
forIndexPath: indexPath) as! ListCell
The dequeue method version with index path never returns nil (creates new cells automatically, when needed).
The force cast as! either succeeds with a non-optional value, or crashes the app (in this case if the cell was not actually ListCell).
it should be
var cell: ListCell? = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as! ListCell
I have a UITableView which is populated by the cells coming from another XIB file. How can I access the views (eg. label) of the cell inside didSelectRowAtIndexPath method of tableView?
Use cellForRowAtIndexPath:
let cell = tableView.cellForRowAtIndexPath(indexPath) as! YourCustomCell
then you can access its label:
cell.customLabel.text = "test"