Zach Chandler Zach Chandler - 4 years ago 854
Swift Question

Multiple Custom UITableViewCell Overwriting Eachother Swift

I am attempting to create a table view with multiple Custom UITableViewCells. If I only use one custom cell the correct cell is displayed. If I add my second cell, The display second cell overwrites the first and inhibits interaction, and the second cell displays default data of the first custom cell until it is selected then it displays the correct cell.

class ViewController: UIViewController {

@IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let nib1 = UINib(nibName: "AccountCell1", bundle: nil)
tableView.registerNib(nib1, forCellReuseIdentifier: "SettingCell1")
let nib2 = UINib(nibName: "AccountCell2", bundle: nil)
tableView.registerNib(nib2, forCellReuseIdentifier: "SettingCell2")
//removing empty rows
tableView.tableFooterView = UIView()
}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}

func numberOfSectionsInTableView(tableView: UITableView) -> Int {

return 1
}

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

return 2
}


func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
/*let cell = tableView.dequeueReusableCellWithIdentifier( "AccountCell", forIndexPath: indexPath) as! UITableViewCell*/
let cell1 = tableView.dequeueReusableCellWithIdentifier( "SettingCell1", forIndexPath: indexPath) as! AccountCell1Controller
let cell2 = tableView.dequeueReusableCellWithIdentifier( "SettingCell2", forIndexPath: indexPath) as! AccountCell2Controller

// Configure the cell...
if(indexPath.row == 0)
{
let imageName = "Amanda.jpeg"
let image = UIImage(named: imageName)
cell1.UserImage.image = image
cell1.UserLabel.text = "@AmandaSmith"
cell1.setNeedsLayout()
return cell1
}
else
{
return cell2
}


}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if(section == 0)
{
return "John Smith"
}
else if(section == 1)
{
return "Settings"
}
else if(section == 2)
{
return "About"
}
return ""
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if(indexPath.row == 0)
{
return 204
}
else if(indexPath.row == 1)
{

return 61
}
else {

return 44
}
}


}


My Account cell controllers, 2 is two buttons with corresponding actions

class AccountCell1Controller: UITableViewCell {
@IBOutlet weak var UserImage: UIImageView!
@IBOutlet weak var UserLabel: UILabel!

override func awakeFromNib() {
super.awakeFromNib()
let user = UserData.self
// Initialization code
}

override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)

// Configure the view for the selected state

}

}

Answer Source

The issue is that you're dequeuing cell1 even when you're not making a cell for that row.

Try only dequeuing the cell that you actually need for the given indexPath.row:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
  // Configure the cell...

  if(indexPath.row == 0) {
    let cell1 = tableView.dequeueReusableCellWithIdentifier( "SettingCell1", forIndexPath: indexPath) as! AccountCell1Controller
    let imageName = "Amanda.jpeg"
    let image = UIImage(named: imageName)
    cell1.UserImage.image = image
    cell1.UserLabel.text = "@AmandaSmith"
    cell1.setNeedsLayout()
    return cell1
  } else {
    let cell2 = tableView.dequeueReusableCellWithIdentifier( "SettingCell2", forIndexPath: indexPath) as! AccountCell2Controller
    return cell2
  }


}

To understand why dequeuing breaks things, it's important to understand how a TableView works.

Let's say you have 100 cells in a table. It would be wasteful to create and manage all 100 of those cells, if your device (say iPhone) can only display 10 at a time. Dequeuing works based on this principle, that you only need about as many cells allocated, as you're ever going to display. So each time you need a new cell (each time cellForRowAtIndexPath is called) you only want to dequeue the cell type that you need, and if a cell is leaving the screen at the same time, you can reuse it, instead of deleting it and going through the (potentially) heavy process of creating a new cell.

Knowing this, I'm still a little hazy on why dequeuing two cells for a single index breaks the layout, but it clearly confused the TableView. As a general rule of thumb when dealing with multiple custom cells in a single tableView, it's best to split out cellForRowAtIndexPath into separate functions for each type of cell (and possibly for different sections as well, if that makes sense). This way you can switch on indexPath.row and call your own custom dequeueThisSettingsCell or dequeueOtherSettingsCell depending on the row, returning the cell back to cellForRowAtIndexPath which then returns it to the tableView. This also greatly shortens cellForRowAtIndexPath, making it clear what kind of cell is dequeued for what row/section, and separates the setup of each cell into different functions (which helps with clarity and readability).

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download