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.
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.
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.
Inside my cell for row at indexPath, I have been using the following code to do most of my work because that is what I have been taught. I was wondering, is it necessary to always use if let to do this work? Because I never find that I ever fall into the else statement.
When would I need to use if let or just let inside cellForRowAtIndexPath?
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCellWithIdentifier("myCustomCell") as? myCustomCell {
} else {
return myCustomCell()
}
}
UITableView has two dequeue modes:
dequeueReusableCell(withIdentifier:): The table view tries to dequeue a cell. If there are none, it will try to create one using the cell you registered with the reuseIdentifier. If you didn't register a cell, it will return nil giving you the chance to create one yourself.
This is where the else clause in your code would come into effect. Basically never, since presumably you did register a cell class. Most likely in the Storyboard (by setting the identifier in the inspector) but you can also do it in code.
dequeueReusableCell(withIdentifier:for:), note the additional parameter. The table view tries to dequeue a cell: If there are none, it will try to create one using the cell class you registered using the reuseIdentifier. If you didn't register a cell, it will crash.
Solution
If you can guarantee that a) the cell is correctly registered and b) the type of the cell is set correctly, you would generally use the second option. This avoids the exact issue you're having:
let cell = tableView.dequeueReusableCellWithIdentifier("myCustomCell", forIndexPath: indexPath) as! myCustomCell
(However, it is still perfectly fine to use if let even in this case.)
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
I'm implementing logic for returning specific cells in a table view call. What suggestions exist for covering the "default" case, if I'm using a switch statement?
See example here:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
switch indexPath.section {
case 0: // CompanyDetailsGeneralTableViewCell
let cell = tableView.dequeueReusableCellWithIdentifier("CompanyDetailsGeneral", forIndexPath: indexPath) as! CompanyDetailsGeneralTableViewCell
// STUFF FOR CompanyDetailsGeneralTableViewCell
return cell
case 1:
let cell = tableView.dequeueReusableCellWithIdentifier("CompanyResources", forIndexPath: indexPath) as! CompanyResourcesTableViewCell
return cell
default:
// Can't reach here (never!) as table has only 2 sections.
return UITableViewCell() // Hack. BEST PRACTICE?
}
}
Among the approaches I've considered:
Not using the switch at all, but it seems more structurally fitting for a switch.
Throwing a runtime exception. Not possible as runtime exceptions are not exciting in Swift.
returning nil - Can't do that, as this function does not return an optional.
Would appreciate comments and feedback.
You should be able to call terminating functions like assertionFailure(), preconditionFailure() or fatalError("Unexpected section in TableView") in order to terminate the application in that scenario.
I do the same thing that you do. It may not display anything but at least the app won't crash which is important to users.