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.
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.
I have UITableView and the contect is Dynamic Prototype.
so I have 5 cells and each cell has it own identifier.
so when I try to return the value in (cellForRowAt)
it would let me.
would please help with that ?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if (indexPath.section) == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as UITableViewCell!
// cell?.textLabel!.text = objectsArray[indexPath.section].sectionObjects[indexPath.row]
return cell!
}
else if (indexPath.section) == 2 {
let cell3 = tableView.dequeueReusableCell(withIdentifier: "cellThree") as UITableViewCell!
return cell3!
}
else if (indexPath.section) == 3 {
let cell4 = tableView.dequeueReusableCell(withIdentifier: "cellFour") as UITableViewCell!
return cell4!
}
else if (indexPath.section) == 4 {
let cell5 = tableView.dequeueReusableCell(withIdentifier: "cellFive") as UITableViewCell!
return cell5!
}
return cell!
}
Thanks !
New Update :-
so the error that is showing to me is (Use of unresolved identifier 'cell')
so when I return at the end (return cell!) it shows this error.However, if I deleted that line it shows me another error asking me to return a value
so bottomline I'm confised what value should I return at the end of (cellForRowAt).
Return values
This method can only return one cell each time it's called. The method will be called each time a new cell is about to appear on the screen. Take a look at the method signature:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
That UITableViewCell at the end is the expected return value and in this case it's a single cell.
The error
Let's take a look at your error:
Use of unresolved identifier 'cell'
"Unresolved identifier" means that it has no idea what "cell" is as it has not been declared in the current scope. But don't you declare cell in this method? Yes you do, but they are in a separate scope. Variable scope defines how long a variable lives and where it can be seen. You can tell when a new variable scope is declared when you see a new set of curly brackets {}, which each of your if statements declare. Variables declared within each set of curly brackets can only be seen by code contained within those brackets. Once execution leaves those brackets, those variables are gone. Let's take a look at what variables are viewable in the scope of your final return statement by removing the scopes created by your if statements:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return cell!
}
Now it is clear that cell doesn't exist at the point of the return. You'll need to declare cell in the method's scope and give it some value. Please note that force unwrapping your cell optionals will likely lead to a crash at runtime as the method must not return nil. You'll want to create cells when tableView.dequeueReusableCell() returns nil.
EDIT: I just realized you aren't using the for indexPath parameter in dequeueReusableCellWithIdentifier. Use that, otherwise it's the wrong one. See:
https://developer.apple.com/documentation/uikit/uitableview/1614878-dequeuereusablecellwithidentifie
https://www.natashatherobot.com/ios-using-the-wrong-dequeuereusablecellwithidentifier/
#Nawaf you can't return multiple values from any function, hence you can't return multiple cells from one call of cellForRow. You may be misunderstanding how cellForRowAtIndexPath works. Right now your code is not populating anything in the cells.
Use of unresolved identifier 'cell'
Furthermore, your error can be occurring for multiple reasons. For one, check that your view controller is correctly assigned to the one in storyboard and that you have assigned a reuseIdentifier in storyboard.
See the links for more info:
https://developer.apple.com/documentation/uikit/uitableviewdatasource/1614861-tableview?language=objc
How does cellForRowAtIndexPath work?
Also a side note for code quality: don't force cast to the cell because that can cause unintended errors. Use conditional binding instead:
guard let cell = tableView.dequeueReusableCellWithIdentifier(...) as? UITableViewCell else {
// What to do when the cast failed
}
Good luck :)
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.)
I'm using Tableview controller to make two prototype cells that app crash due to
thread 1 exc_bad_instruction (code=exc_i386_invop subcode=0x0)
pointed to >>> return cell! in the first cell
fatal error: unexpectedly found nil while unwrapping an Optional value
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 10
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if (indexPath.section == 0) {
let cell = tableView.dequeueReusableCellWithIdentifier("one", forIndexPath: indexPath) as? one
cell?.textLabel?.text = "book"
return cell!
} else {
let cell = tableView.dequeueReusableCellWithIdentifier("two", forIndexPath: indexPath) as! two
cell.textLabel?.text = "not a book"
return cell
}
If I may improve the code:
let cellID = (indexPath.section == 0) ? "one" : "two"
if let cell = tableView.dequeueReusableCellWithIdentifier(cellID, forIndexPath: indexPath)
as? UITableViewCell
cell.textLabel!.text = (cellID == "one") ? "book" : "not a book"
return cell
}
else {
assertionFailure("Unable to dequeue a cell of ID \(cellID)")
return nil
}
Then you'll get sensible error behavior. I'll bet that, even though you've named your classes one and two, that you have neglected to set their 'Identifier' within StoryBoard in the Attributes Inspector for each cell prototype.
Your answer is very wrong. You should not be creating lots of table cells. This is a misuse of tables. You should only use the ones on the reuse queue.
Use my version of the code, and set a breakpoint just after the if let cell. Analyze the object in the debugger and see what type it really is.
If instead you hit the assertionFailure, you still haven't really set the Identifier properly in StoryBoard. Show us a screen capture to convince us.
Looks like it's because the call
let cell = tableView.dequeueReusableCellWithIdentifier("two", forIndexPath: indexPath) as! two
is doing a forced down cast of the cell to type two, and the object returned is not of type two. Using ! here is basically saying:
"Dequeue a cell (returns type AnyObject) and then downcast it two type two. I know down casts can fail if the type doesn't match, but in this case I'm sure it will. No need to handle errors"
As casts can fail, the cast returns an optional. In this case the cell returned can't be downcast as the types don't match. Check the code that registers the identifier "two", and check you are registering the right type of class (A UITableViewCell subclass named two)
I think you should edit code :
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 2
}