Force downcast when programming a custom UITableViewCell in Swift - ios

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()
}

Related

swift Non-void function should return a value in a guard let

guard let cell = tableView
.dequeueReusableCell(withIdentifier: cellIdentifier) as? FolderAndFileCell else {
print("some")
return
}
It says that
Non-void function should return a value
What should I return here?
Inside cellForRowAt you have to
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) as? FolderAndFileCell else {
print("some")
return UITableViewCell()
}
This signature
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell
should has a non void return value
The well-know approach is
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) as! FolderAndFileCell
This is an instance where you should be doing this instead.
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) as! FolderAndFileCell
You shouldn't need to use guard because I assume you are wanting to set the cell to FolderAndFileCell. If you can't set the cell to that, and go ahead and return a UITableViewCell() you are going to just get an empty cell and not know the reasoning.
I suggest you force cast to FolderAndFileCell and deal with the error if it presents then simply returning an empty cell if an error setting the cell is present.

tableView.cellForRow(at: indexPath ) always nil?

I never get this print . it is like is always nil, doesnt matter how much i scroll it up or down :/
if let update = tableView.cellForRow(at: indexPath ) as? RestCell {
print("VISIBLE CELL")
}
Complete code..
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : RestCell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! RestCell
// Pass data to Cell :) clean the mess at the View Controller ;)
cell.restData = items[indexPath.row]
// Send cell so it can check update the image to the right cell ;)
// cell.cell = tableView.cellForRow(at: indexPath ) as? RestCell
//print("LA CELDA ES \(tableView.cellForRow(at: indexPath ))")
if let update = tableView.cellForRow(at: indexPath ) as? RestCell {
print("VISIBLE CELL")
}
return cell
}
You have not returned your cell from cellForRow method so this is the reason why your if statement is returning false. I'm not sure why you are trying to do this but there have to be a better way. If you want to look for visible cells you can use UITableView visibleCells variable.

convert String content to UITableViewCell type in swift

Can you convert the content of a Swift 3 String into a type through a specific function? I'll include an example:
I've declared multiple UITableViewCell classes as follows:
class ScrollFeedCell : UITableViewCell {...}
class AdCell : UITableViewCell {...}
class MovieCell : UITableViewCell {...}
I want to declare the conversion function, in my view controller, as follows:
func convert(String) -> Any {}
Then I want to use the following:
class TableView : UITableViewController {
let typeArray = [String]
override func viewDidLoad() {
//add a huge ton of strings into the typeArray
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = UITableViewCell()
let c = typeArray[indexPath.section]
if c == "ScrollFeddCell" || c == "AdCell" || c == "MovieCell" {
cell = tableView.dequeueReusableCell(withIdentifier: content[indexPath.section], for: indexPath) as! convert(c)
} else {
cell = tableView.dequeueReusableCell(withIdentifier: "CategoryScrollFeed_Cell", for: indexPath)
}
return cell
}
}
I do not think this is possible. Even if it is somehow possible, I think it is going to involve lots of dirty tricks which is not really worth it in this situation.
In fact, the only place you used your imaginary convert method is here:
cell = tableView.dequeueReusableCell(withIdentifier:
content[indexPath.section], for: indexPath) as! convert(c)
^^^^^^^^^^
Why do you want to cast it to the right type? Since this is very dynamic, the compiler can't know what members will the type returned by convert have. Basically, too dynamic. It is not useful to cast it to the right type here.
The enclosing method returns a UITableViewCell anyway, so you can just return the return value of dequeueResuableCell without the compiler complaining.
"But I want to configure the cell after dequeuing it though..." you might say.
Well, you are going to configure a ScrollFeedCell in a different way from a MovieCell, right? So you can't just write all the configuration code after this line:
cell = tableView.dequeueReusableCell(withIdentifier:
content[indexPath.section], for: indexPath) as! convert(c)
You still have to write an if statement and check whether the cell is a MovieCell, ScrollFeedCell or AdCell. So why not delete the above line and do this instead:
if c == "ScrollFeedCell" {
let scrollFeedCell = tableView.dequeueReusableCell(withIdentifier:
content[indexPath.section], for: indexPath) as! ScrollFeedCell
// configure cell here
cell = scrollFeedCell
} else if c == "AdCell" {
let adCell = tableView.dequeueReusableCell(withIdentifier:
content[indexPath.section], for: indexPath) as! AdCell
// configure cell here
cell = adCell
} else if c == "MovieCell" {
let movieCell = tableView.dequeueReusableCell(withIdentifier:
content[indexPath.section], for: indexPath) as! MovieCell
// configure cell here
cell = movieCell
} else {
cell = tableView.dequeueReusableCell(withIdentifier: "CategoryScrollFeed_Cell", for: indexPath)
}
Edit:
Try this:
if c == "ScrollFeedCell" {
let scrollFeedCell = tableView.dequeueReusableCell(withIdentifier:
content[indexPath.section], for: indexPath) as! ScrollFeedCell
scrollFeedCell.delegate = self
cell = scrollFeedCell
} else if c == "AdCell" || c == "MovieCell" { // add your other cell types here.
cell = tableView.dequeueReusableCell(withIdentifier:
content[indexPath.section], for: indexPath)
} else {
cell = tableView.dequeueReusableCell(withIdentifier: "CategoryScrollFeed_Cell", for: indexPath)
}
Please consider what you want to do is necessary or not. Why you want to convert them to specific cell type? It will work just keep the cell as UITableViewCell and return it. If you have specific actions for different cells, you should separate the if cases:
if c == "ScrollFeddCell" {
cell = tableView.dequeueReusableCell(withIdentifier: c, for: indexPath) as! ScrollFeddCell
//cell.doSomethingForScorll()
}
else {
cell = tableView.dequeueReusableCell(withIdentifier: c, for: indexPath)
//do nothing for common cells.
}
//....
A little late, but for those looking for an answer:
I know what you want, and I agree with your need.
In my case, I need to do this because in my app, I not only receive the data from the server, but ALSO the layout of such data inside the cell. So far, I haven't been able to find a solution. In your case, it seems a little easier:
// 1. Create a protocol with the configuring method
protocol configureCellProtocol
{
func configure(cell:MyData)
}
// 2. Add this protocol to the 8 classes derived from UITableViewCell that define your cells
// 3. Cast the reusable cell to this protocol: (note that you have to do a double cast,
// both to configureCellProtocol and UITableViewCell, (that's what the & is for) otherwise,
// you won't be able to return the configured cell
let thisCell=tableView.dequeReusableCell(
withReuseIdentifier: cellClass, for: indexPath) as! UITableViewCell & configureCellProtocol
// 4. then you can call the method like this:
thisCell.configure(cell:configurationData)
// which in turn will call the specific method in each class. Then you have to define the configure
// method in each class, otherwise you'll get a crash at runtime. Do what you need to configure
// your cells in each class in this method
//
in step 3, cellClass is a String, which in turn is the class name that you register. In your case, you would have to select it from an array according to the criteria that makes every cell different

Swift 3 Generic cannot convert value of type UITableViewCell

I have a custom cell that can accept a generic type.
class MyCell<T>: UITableViewCell where T: MyCellDataSource, T: MyCellDelegate
How do I use it or type cast or infer it when dequeueing a cell?
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
In Swift 3.0
I had recently worked with the same implementation.
I solved this by the following a code.
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? MyCell<TName>
//Configure cell here
return cell!
Hope it helps

Tailor forced casts conflicts

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

Resources