Winn Stone
Winn Stone

Reputation: 37

Show JSON data correctly in UITableView

I've made an UITableView and filled it with JSON data I get inside my API. I get and place all correctly but when I scroll or delete a row everything gets messed up!

Take a look at the screenshot

Labels and images interfere; this is my code:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
    var dict = productsArrayResult[indexPath.row]

    let cellImage = UIImageView(frame: CGRect(x: 5, y: 5, width: view.frame.size.width / 3, height: 90))
    cellImage.contentMode = .scaleAspectFit
    let productMainImageString = dict["id"] as! Int
    let url = "https://example.com/api/DigitalCatalog/v1/getImage?id=\(productMainImageString)&name=primary"
    self.downloadImage(url, inView: cellImage)
    cell.addSubview(cellImage)

    let cellTitle = UILabel(frame: CGRect(x: view.frame.size.width / 3, y: 5, width: (view.frame.size.width / 3) * 1.9, height: 40))
    cellTitle.textColor = UIColor.darkGray
    cellTitle.textAlignment = .right
    cellTitle.text = dict["title"] as? String
    cellTitle.font = cellTitle.font.withSize(self.view.frame.height * self.relativeFontConstantT)
    cell.addSubview(cellTitle)

    let cellDescription = UILabel(frame: CGRect(x: view.frame.size.width / 3, y: 55, width: (view.frame.size.width / 3) * 1.9, height: 40))
    cellDescription.textColor = UIColor.darkGray
    cellDescription.textAlignment = .right
    cellDescription.text = dict["description"] as? String
    cellDescription.font = cellDescription.font.withSize(self.view.frame.height * self.relativeFontConstant)
    cell.addSubview(cellDescription)

    return cell
}

Upvotes: 0

Views: 107

Answers (5)

laxman khanal
laxman khanal

Reputation: 998

You are adding subviews multiple times while dequeuing reusable cells. What you can do is make a prototype cell either in storyboard or as xib file and then dequeue that cell at cellForRowAtIndexPath.

Design Prototype cell and assgin with the custom class

Your custom class for cell will look similar to this where outlets are drawn from prototype cell.

Note: You need to assign Reusable Identifier for that prototype cell.

class DemoProtoTypeCell: UITableViewCell { @IBOutlet var titleLabel: UILabel! @IBOutlet var descriptionLabel: UILabel! @IBOutlet var titleImageView: UIImageView! }

Now you can deque DemoProtoTypeCell and use accordingly.

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: DemoProtoTypeCell.self), for: indexPath) as! DemoProtoTypeCell
  cell.titleImageView.image = UIImage(named: "demoImage")
  cell.titleLabel.text = "demoTitle"
  cell.descriptionLabel.text = "Your description will go here."
  return cell
}

Upvotes: 1

Judit
Judit

Reputation: 41

Try this:

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        var cell:UITableViewCell! = tableView.dequeueReusableCell(withIdentifier: "cell")
        if cell == nil
        {
            cell = UITableViewCell.init(style: UITableViewCellStyle.default, reuseIdentifier: "cell")
        }
        for subView in cell.subviews
        {
            subView.removeFromSuperview()
        }
// Your Code here
    return cell
    }

Upvotes: 0

Amset
Amset

Reputation: 86

I think the other answers already mentioned a solution. You should subclass the tableview cell and just change the values of your layout elements for each row.

But I want to explain why you get this strange behaviour.

When you call tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) it tries to reuse an already created cell with the passed identifier @"cell". This saves memory and optimises the performance. If not possible it creates a new one.

So now we got a cell with layout elements already in place and filled with your data. Your code then adds new elements on top of the old ones. Thats why your layout is messed up. And it only shows if you scroll, because the first cells got no previous cells to load.

When you subclass the cell try to create the layout only once on first initialisation. Now you can pass all values to the respective layout element and let the tableview do its thing.

Upvotes: 0

Amit
Amit

Reputation: 4886

I have used below method to remove all subviews from cell:

override func prepareForReuse() {
    for views in self.subviews {
        views.removeFromSuperview()
    }
}

But I have created UITableViewCell subclass and declared this method in it.

you can also do one thing as @sCha has suggested. Add tags to the subviews and then use the same method to remove subview from cell:

override func prepareForReuse() {
    for view in self.subviews {
        if view.tag == 1 {
          view.removeFromSuperview()
        } 
    }
}

Hope this helps.

Upvotes: 0

sCha
sCha

Reputation: 1504

That's because you are adding subviews to reused (so that it may already have subviews added previously) cells.

Try to check if the cell has subviews and fill in information you need, if there're no subviews then you add them to the cell.

Option 1

if let imageView = cell.viewWithTag(1) {
    imageView.image = //your image
} else {
    let imageView = UIImageView(//with your settings)
    imageView.tag = 1
    cell.addSubview(imageView)
}

Option 2

Crete UITableViewCell subclass that already has all the subviews you need.

Upvotes: 0

Related Questions