Using swift for iOS, here is my cellforRow method
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.section {
case 0: // Cover Image
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifierCover) as? AddBlogCoverTableViewCell else { return UITableViewCell() }
cell.layer.backgroundColor = UIColor.clear.cgColor
cell.selectionStyle = .none
cell.indexPath = indexPath
cell.delegate = self
cell.titleTextField.delegate = self
cell.coverImageView.image = coverImage
cell.titleTextField.placeholder = "Title"
cell.titleTextField?.text = articleName ?? ""
cell.setBorder(of: 0.7, ofColor: UIColor.lightGray.cgColor)
return cell
case 1: // rest story
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) as? AddBlogTableViewCell else { return UITableViewCell() }
cell.layer.backgroundColor = UIColor.clear.cgColor
cell.selectionStyle = .none
cell.indexPath = indexPath
cell.delegate = self
cell.captionTextView.delegate = self
cell.captionTextView.tag = indexPath.row
cell.tag = indexPath.section
cell.media = cards?[indexPath.row].media
cell.captionTextView.text = cards?[indexPath.row].caption ?? ""
showLocationSuggestions(at: indexPath, in: cell)
cell.poi = cards?[indexPath.row].poi
return cell
default:
return UITableViewCell()
}
return UITableViewCell()
}
How should I avoid repeating the code that I have to provide to the cell in all cases of switch
?
2 Answers 2
Defensive programming (never force unwrap, no forced cast, ...) is good and important to handle "runtime problems" gracefully: Unexpected user input, failed network connections, I/O errors, and many more.
But programming errors are a different category. Example 1: If
tableView.dequeueReusableCell(withIdentifier: cellIdentifierCover)
fails, or does not return a AddBlogCoverTableViewCell
, then you did
not configure the table view correctly in the Storyboard. Such cases
should be detected during development. Therefore it is better to abort
with a runtime error instead of "hiding" the problem and returning
a plain UITableViewCell
. This is a valid use-case for a forced cast:
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifierCover) as! AddBlogCoverTableViewCell
or better:
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifierCover, for: indexPath) as! AddBlogCoverTableViewCell
Example 2: Your table view has 2 sections, so indexPath.section
must be 0
or 1
, every other value would be a programming error.
Again, fail early instead of hiding the problem:
switch indexPath.section {
case 0:
// do something ...
case 1:
// do something ...
default:
fatalError("Unexpected section \(indexPath.section)")
}
Note also that your final
return UITableViewCell()
is never be executed.
Now for possible simplifications. Both cell classes have some properties in common, these can be defined in a common superclass:
class AddBlogCommonCell : UITableViewCell {
var indexPath: IndexPath!
var delegate: UITableViewController!
// ...
}
class AddBlogCoverTableViewCell : AddBlogCommonCell {
// ...
}
class AddBlogTableViewCell : AddBlogCommonCell {
// ...
}
Then you can configure the specific properties inside the switch statement, and the shared properties outside the switch statement, thereby avoiding the code repetition:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: AddBlogCommonCell
switch indexPath.section {
case 0:
let coverCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifierCover) as! AddBlogCoverTableViewCell
coverCell.titleTextField.delegate = self
coverCell.coverImageView.image = coverImage
// ...
cell = coverCell
case 1:
let storyCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) as! AddBlogTableViewCell
storyCell.captionTextView.delegate = self
storyCell.captionTextView.tag = indexPath.row
// ...
cell = storyCell
default:
fatalError("Unexpected section \(indexPath.section)")
}
// AddBlogCommonCell properties:
cell.delegate = self
cell.indexPath = indexPath
// ...
// UITableViewCell properties:
cell.layer.backgroundColor = UIColor.clear.cgColor
cell.selectionStyle = .none
// ...
return cell
}
-
\$\begingroup\$ Thanks a lot for the detailed answer. It's very helpful. But I am using xib, and its a bit tricky to subclass cells using xib. \$\endgroup\$saurabh– saurabh2017年12月02日 05:51:06 +00:00Commented Dec 2, 2017 at 5:51
-
\$\begingroup\$ Also don't switch on case 0,1 name your constants: CoverSection, StorySection \$\endgroup\$trapper– trapper2018年06月05日 08:02:27 +00:00Commented Jun 5, 2018 at 8:02
The easiest and the fastest way that I see is to use a configuration closure.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let configureCell = { (cell: MyGenericTableViewCell) in
// Generic configurartion
}
switch indexPath.section {
case 0: // Cover Image
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifierCover) as? AddBlogCoverTableViewCell else { return UITableViewCell() }
configureCell(cell)
// Perform cell-specific config
return cell
case 1: // rest story
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) as? AddBlogTableViewCell else { return UITableViewCell() }
configureCell(cell)
// Perform cell-specific config
return cell
default:
return UITableViewCell()
}
return UITableViewCell()
}
Also, some part of your configuration, such as:
cell.layer.backgroundColor = UIColor.clear.cgColor
cell.selectionStyle = .none
can be put inside initialization methods of the cells.