When I was learning about tableviews the tutorials I followed used the following code-
guard let cell = tableView.dequeueReusableCell(withIdentifier: Self.reminderListCellIdentifier, for: indexPath) as? ReminderListCell else {
return UITableViewCell()
}
I just came across example code from Apple which is;
guard let cell = tableView.dequeueReusableCell(withIdentifier: Self.reminderListCellIdentifier, for: indexPath) as? ReminderListCell else {
fatalError("Unable to dequeue ReminderCell")
}
What should I implement? fatalError causes a crash I believe. Is the the desired behaviour?
None of the suggestions. Force unwrap the cell
let cell = tableView.dequeueReusableCell(withIdentifier: Self.reminderListCellIdentifier, for: indexPath) as! ReminderListCell
In practice it causes the same behavior as the fatalError.
The code must not crash if everything is hooked up correctly. The potential mistake is a design mistake.
The first snippet is pretty silly because in case of the mentioned design mistake nothing will be displayed and you have no idea why.
Force unwrapping is not evil per se.
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 am new to the iOS programming scene and I recently came across some code examples online of implementations like:
functableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: customCellIdentifier, for: indexPath) as? CustomCell
if (cell == nil) {
cell = CustomCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: customCellIdentifier) asCustomCell
}
...
}
Where the author tried to handle the event where dequeueReusableCell return nil.
But from my limited personal experience with UITableView and custom cells, I have yet to encounter a time when dequeueReusableCell returned me nil.
From researching, I found the reason could be
"The dequeue… methods try to find a cell with the given reuse
identifier that is currently offscreen. If they find one, they return
that cell, otherwise they return nil."
from MrTJ's answer here
But that has never happened once to me. When I purposely give it a wrong identifier, a runtime error would occur but not once was nil returned. I wonder when exactly that would happen and if handling it is really necessary.
That code isn't correct. The older form of dequeueReusableCell without the indexPath parameter returns nil when there isn't an available cell in the reuse pool (i.e. when the table view first appears). In that case it is your responsibility to allocate a cell.
The newer form of dequeueResuableCell, shown in your question, will always return a cell as it allocates a cell if required.
The expression in your question can return nil if the conditional downcast fails (that is, the cell that was returned wasn't an instance of CustomCell).
I would argue that this represents a serious misconfiguration somewhere and should be found during development. For this reason a forced downcast is normally used; during development you get a crash, fix the problem and move on.
let cell = tableView.dequeueReusableCell(withIdentifier: customCellIdentifier, for: indexPath) as! CustomCell
The code in your question is some sort of Frankenstein mixture of the old and the new.
How do I change the following closure so that cell is 'weak'? :
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath as IndexPath) as? PlayerTableViewCell else {
fatalError("The dequeued cell is not an instance of PlayerTableViewCell")
}
I am sure there is a simple way to achieve this but I have been unable to determine the correct way to handle this.
Thanks
A duplicate call to dequeueReusableCell was causing a memory consumption problem, which led me to think I had a strong reference that needed to be weakened.
Removing this erroneous extra call solved the underlying issue I thought needed a fix.
In the following code:
let cell = tableView.dequeueReusableCell(
withIdentifier: "MyCell",
for: indexPath
) as MyTableViewCell // 'UITableViewCell' is not convertible to 'MyTableViewCell'; did you mean to use 'as!' to force downcast?
We have the error complaining that UITableViewCell is not convertible to a MyTableViewCell.
So the compiler suggests to do a force cast:
let cell = tableView.dequeueReusableCell(
withIdentifier: "MyCell",
for: indexPath
) as! MyTableViewCell // ?!?!?!
However, that feels ugly.
Is there no alternative to force casting when dealing with tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)? Is this really the most idiomatic way to accomplish this in Swift?
Thanks!
Is this really the most idiomatic way
Absolutely. It's perfectly standard.
You could downcast safely like this:
if let cell = tableView.dequeueReusableCell(
withIdentifier: "MyCell",
for: indexPath
) as? MyTableViewCell {
But this is one situation where I don't think that's worth doing, because if this turns out not to be a MyTableViewCell, there's a very real sense in which you want to crash.
You can do it like this:
guard let cell = tableView.dequeueReusableCell(
withIdentifier: "MyCell",
for: indexPath
) as? MyTableViewCell else {
// Log an error, or fatalError("Wrong cell type."), etc.
// or maybe return UITableViewCell()
}
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! CustomTableViewCell
It's standard sentence that implements the table view's cell's properties. But Tailor (it's a Swift analyzer/linter) warns about you shouldn't forced the CustomTableViewCell as as! If I used to as as?, I have to implement cell's properties as cell!. But Tailor don't warn about [forced-type-cast] Force casts should be avoided. What's the reason of this? How can I implement cell's without unwrap of cell as cell! What's the correct programming paradigms for forced casts operations in Swift?
I am not familiar with "Tailor" but most likely the reason it is giving you this warning is because if a force cast fails then obviously your program will crash and thats never good.
The as! operator does have its place if you are 100% sure that what you are casting is of that type. But, even then its better to be safe than sorry and you should use a guard or if let statement instead in order to handle a failed cast.
if let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as? CustomTableViewCell {
//do what you like with cell
}
or
guard let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as? CustomTableViewCell else {
//abort current scope, return, break, etc. from scope.
}
//do what you like with cast cell