How to show 2 customized cells in the UITableView - ios

I have to show 2 different cells in a table. I have tried it by setting a prototype to a table. but it is still showing Prototype table cells must have reuse identifiers warning.
could someone please guide me to resolve this warning.
Followed this link:
UITableview with more than One Custom Cells with Swift

In storyboard you have to define the Identifier for the cells like the below image
Then in cellForRowAtIndexPath you have to use the specific identifier for specific cell like this
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Identifier1")
//set the data here
return cell
}
else if indexPath.row == 1 {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Identifier2")
//set the data here
return cell
}
}

You must set the Reuse Identifier for both prototype cells, and they must be different. Then in your cellForItemAtIndexPath method, you must dequeue the cells using the corresponding Reuse Identifier based on the indexPath given.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableView {
switch indexPath.section {
case 0:
return tableView.dequeueReusableCellWithIdentifier("CustomCell1", forIndexPath: indexPath)
case 1:
return tableView.dequeueReusableCellWithIdentifier("CustomCell2", forIndexPath: indexPath)
break:
return UITableViewCell()
}
}

Related

Swift: How to access a label in a custom cell within a certain tableView section

I have a custom UITableViewCell with a UILabel called CellLabel. I want to be able to access this label at a certain tableView section e.g. section 3 and provide it a string to display without the use of an array and arrayName[indexPath.section].
You can catch it in cellForRowAtIndexPath override of a TableView
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:UITableViewCell = tableView.dequeueReusableCellWithIdentifier("YourCustomCellRegisterName") as YourCustomCell
if(indexPath.section == 3){
cell.CellLabel.text = "foo"
}
return cell
}

How to dequeue UITableViewCells that conform to a defined protocol

I'd like to make sure that my tableview only contains cells that conform to a said protocol. I simplified the implementation to illustrate the specific problem.
protocol ACommonLookAndFeel {
func configureMyLookAndFeel()
}
CellA: UITableViewCell, ACommonLookAndFeel
CellB: UITableViewCell, ACommonLookAndFeel
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: UITableViewCell
if indexPath.row == 0 {
cell = tableView.dequeueReusableCellWithIdentifier("CellA", forIndexPath: indexPath) as! CellA
if let myCell = cell as? CellA {
myCell.configureMyLookAndFeel() // we need to call this for each cell
}
} else if indexPath.row == 1 {
cell = tableView.dequeueReusableCellWithIdentifier("CellB", forIndexPath: indexPath) as! CellB
if let myCell = cell as? CellB {
myCell.configureMyLookAndFeel() // we need to call this for each cell
}
}
return cell
}
The above code is working except that there is repeated code and I need to do the casting each time to access the configureMyLookAndFeel() method. As I want all my cells to be configured for the look and feel, I tried the code below instead but hit the compile error
Error: Cannot convert return expression of type 'protocol' to return type 'UITableViewCell'
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: protocol <ACommonLookAndFeel>
if indexPath.row == 0 {
cell = tableView.dequeueReusableCellWithIdentifier("CellA", forIndexPath: indexPath) as! CellA
} else if indexPath.row == 1 {
cell = tableView.dequeueReusableCellWithIdentifier("CellB", forIndexPath: indexPath) as! CellB
}
cell.configureMyLookAndFeel() // works
return cell // Compiler Error !
}
Is there a way to fix this compiler error?
Ideally I wouldn't have like to avoid repeating the call to dequeueCell and the casting to CellA or CellB. I know what cell I need to cast it to based on the cellReuseIdentifier which is the same as my cell class name. Is there a way?
Thanks!
Try with this:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: UITableViewCell
if indexPath.row == 0 {
cell = tableView.dequeueReusableCellWithIdentifier("CellA", forIndexPath: indexPath) // There is no need to cast here
} else if indexPath.row == 1 {
cell = tableView.dequeueReusableCellWithIdentifier("CellB", forIndexPath: indexPath) // There is no need to cast here
}
// The method will be called for all cells that conform to ACommonLookAndFeel.
// This is also safe, so no crash will occur if you dequeue a cell that
// doesn't conform to ACommonLookAndFeel. Depending on the behavior
// you want to achieve, you may want to use a ! instead of ? to force
// a crash in case of issues while developing your app.
(cell as? ACommonLookAndFeel)?.configureMyLookAndFeel()
// You have to return a UITableViewCell, not a ACommonLookAndFeel
return cell
}

dequeueReusableCellWithIdentifier:forIndexPath: VS dequeueReusableCellWithIdentifier:

I have read this question and think that I understand the difference between the two methods until I find a strange example:
Set table view cell's style be Basic, Identifier be Cell in Storyboard, code as below:
import UIKit
class TableViewController: UITableViewController {
var items: [String]!
override func viewDidLoad() {
super.viewDidLoad()
items = ["first", "second", "third"]
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// either works fine
let cell = tableView.dequeueReusableCellWithIdentifier("Cell")! // let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
cell.textLabel?.text = items[indexPath.row]
return cell
}
}
Very simple, but when I change the tableView:cellForRowAtIndexPath: method to 1, 2, 3, 4 cases respectively:
Case 1:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
cell.textLabel?.text = items[indexPath.row]
return cell
}
Case 2:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
cell = tableView.dequeueReusableCellWithIdentifier("Cell")!
cell.textLabel?.text = items[indexPath.row]
return cell
}
Case 3:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("Cell")!
cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
cell.textLabel?.text = items[indexPath.row]
return cell
}
Case 4:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("Cell")!
cell = tableView.dequeueReusableCellWithIdentifier("Cell")!
cell.textLabel?.text = items[indexPath.row]
return cell
}
Case 1, 2 (doesn't work):
Case 3, 4 (works fine):
How to explain? I think it really helps to understand these two methods from another perspective, any opinion is welcome.
In each case, you are dequeueing two cells for each row. In cases 1 and 2, you call the ("Cell", forIndexPath: indexPath) version first. In this case the table view ends up with two cells for each row, one completely overlapping and obscuring the other. You can see this in the view inspector since you can amend the angle of view to see behind:
(I amended the cellForRowAtIndexPath code like this:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("plainCell", forIndexPath: indexPath)
cell.textLabel!.text = "First cell for row \(indexPath.row)"
cell = tableView.dequeueReusableCellWithIdentifier("plainCell", forIndexPath: indexPath)
cell.textLabel!.text = "Second cell for row \(indexPath.row)"
print("Cell being returned is \(cell)")
return cell
}
to given different text labels to each cell.) In cases 3 and 4, where you call the ("Cell") version first, the table view has only one cell for each row.
Why the different behaviour? If you create a custom subclass of UITableViewCell and use that in your storyboard, you can then override various methods and add print() statements to see what's happening. In particular, awakeFromNib, didMoveToSuperView, and deinit. What transpires is that in cases 1 and 2, the first cell is created (awakeFromNib) and immediately added (didMoveToSuperView) to a superview, presumably the table view or one of its subviews. In cases 3 and 4, the first cell is created but is not added to a superview. Instead some time later, the cell is deallocated (deinit).
(Note that if the second cell is dequeued using the ("Cell", forIndexPath: indexPath) version, it too is added immediately to a superview. However, if the second cell is dequeued using the ("Cell") version, it is only added to a superview after the cellForRowAtIndexPath method has returned.)
So the key difference is that the ("Cell", forIndexPath: indexPath) version results in the cell being added immediately to the table view, before even the cellForRowAtIndexPath has completed. This is hinted at in the question/answer to which you refer, since it indicates that the dequeued cell will be correctly sized.
Once added to the superview, the first cell cannot be deallocated since there is still a strong reference to it from its superview. If the cells are dequeued with the ("Cell") version, they are not added to the superview, there is consequently no strong reference to them once the cell variable is reassigned, and they are consequently deallocated.
Hope all that makes sense.
dequeueReusableCellWithIdentifier: doesn't give you guarantees: cells could be nil, so you have to check if your cell is nil and handle it properly or your app will crash.
dequeueReusableCellWithIdentifier:forIndexPath:, on the other hand, does check this for you (it always return a cell).
For your particular case (Swift), this means you can safely force-unwrap the cell with dequeueReusableCellWithIdentifier:forIndexPath:, while you'll have to use the if let syntax with the second one.
Example codes (in Objective-C, I don't use Swift)
dequeueReusableCellWithIdentifier:forIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" atIndexPath:indexPath];
// Here we know the cell is not nil (....atIndexPath: ensures it)
cell.textLabel.text = items[indexPath.row];
return cell;
}
dequeueReusableCellWithIdentifier:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
// You asked for a cell, but you don't know if it is nil or not
// In Swift, here the cell should be a conditional
// First, check if the cell is nil
if ( cell == nil ) {
// Cell is nil. To avoid crashes, we instantiate an actual cell
// With Swift conditional should be something similar
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell"];
}
// Here you're sure the cell is not nil
// If condicional, you probably will write cell?.textLabel?.text = items[indexPath.row];
cell.textLabel.text = items[indexPath.row];
// Finally, you return the cell which you're 100% sure it's not nil
return cell;
}

2 custom UITableViewCell in one UITableView Swift

I am trying to implement a custom table view which has different types of cells: type A and type B. All of my cells should be of type A, except for one that will be of type B. Whenever the users selects a cell, this one changes to type B.
My code is the following one:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let row = indexPath.row
if (row == typeBCellIndex) {
// Get Type B cell
let cell = tableView.dequeueReusableCellWithIdentifier(typeBCellIdentifier, forIndexPath: indexPath) as! TypeBTableViewCell
return cell
} else {
// Get Type A cell
let cell = tableView.dequeueReusableCellWithIdentifier(typeACellIdentifier, forIndexPath: indexPath) as! TypeATableViewCell
cell.detailLabel.text = "I am a type A cell"
return cell
}
}
The variable typeBCellIndex is initialised in 0, and this code gives an error when I add a new cell and try to dequeue the cell at index 1.
In Objective-C, as this links indicates, I would check if the cell is nil, and if not create a new instance of the corresponding cell. However, I am not sure if this concept applies to Swift, and in case it does, I don't know how to do it.
declare a variable cellindex before viewdidload method and initialize to 3 or any number
and in tableview design two different cells and assign unique identifier for each
code for tableview is
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell : UITableViewCell!
var row = indexPath.row
if(row == cellindex){
cell = tableView.dequeueReusableCellWithIdentifier("Cell2", forIndexPath: indexPath) as! UITableViewCell
}else{
cell = tableView.dequeueReusableCellWithIdentifier("Cell1", forIndexPath: indexPath) as! UITableViewCell
}
return cell
}
the cellindex row will be cell2 and other cells are cell1

How to add more than 2 detailTextLabel in TableView (Swift)

I tried to add many details (fields) to display in TableView. by concat each fields and put in for detailTextLabel e.g.:
From:
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell!{
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier:"test")
cell.textLabel.text = taskMgr.tasks[indexPath.row].name
cell.detailTextLabel.text = taskMgr.tasks[indexPath.row].desc
return cell
}
To
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let cell:UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "Test")
cell.textLabel?.text=tasksList[indexPath.row].name
var yString = tasksList[indexPath.row].desc
var xString = tasksList[indexPath.row].amnt
var zString = xString+" "+yString
cell.detailTextLabel?.text=zString
return cell
}
Could anyone point out any other ways for better code for many fields to display in the view.
I think your first method is great. In storyboard inside a prototype cell you can place several UILabels. Then just update each label as you did. You can place labels in a xib or create them in code. But there is nothing wrong with having several labels and then updating them individually. This gives more flexibility that concatenation because you can justify the text in each label as required to make the tableview pretty. So each cell row has good alignment with the one above it.

Resources