Reputation: 21
I am trying to create a Table View using a UITableViewController.
The 'cellForRowAtIndexPath' doesn't seem to be working right-- I have pre-loaded photos and comments from Parse and put them in an array, and want pictures that fill up the screen width, so I am doing some manual calculations on them to resize.
I am doing some unusual things to get the Table View to look right, but now the data is duplicated.
I know there is something here that I'm not getting, but I'm not really sure what it is... maybe I need to do something else to bind the data, or do something with the "prepareForReuse" method?
I have included code from the UITableViewController subclass and the UITableCell subclass I wrote.
import UIKit
class YouTableViewCell: UITableViewCell {
var recipeId:String = ""
var recipeName:String = ""
var imageHeight:CGFloat = 0
var labelHeight:CGFloat = 0
// DO I NEED THIS?
override func prepareForReuse() {
self.textLabel!.text = recipeId //Put your label name
self.imageView!.image = nil
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
class YouTableViewController: BaseTableViewController { // UITableViewController {
var labelCreated = [Bool]()
var imageCreated = [Bool]()
var cellArray = [Int:YouTableViewCell]()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
if !isYouDataLoaded {
_ = DataController.getYouPageData()
}
// initialize height arrays
for _ in 0..<youFeed.count {
labelCreated.append(false)
imageCreated.append(false)
}
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
self.navigationController?.setToolbarHidden(false, animated: false)
}
// ... skipping down
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// return feedArray.count
return youFeed.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
print("cellForRowAtIndexPath : \(indexPath.row)")
// Get reference to cell
cellArray[indexPath.row] = (tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! YouTableViewCell)
let thisCell:YouTableViewCell = cellArray[indexPath.row]!
// TEMP -- Border
thisCell.layer.borderWidth = 2.0
thisCell.layer.borderColor = UIColor.blackColor().CGColor
let meal = youFeed[indexPath.row].recipe
// If the image exists
if let foodImage:UIImage = meal.imageView.image! as UIImage {
var nameLabel:UILabel = UILabel()
var fitImageView:UIImageView?
if imageCreated[indexPath.row] == false {
print("For IMAGE #\(indexPath.row)")
fitImageView = LayoutHelper.fitImageInCell(foodImage, cell: thisCell, name: meal.recipeName)
thisCell.contentView.addSubview(fitImageView!)
thisCell.imageHeight = fitImageView!.frame.height
fitImageView!.frame.origin.y = 0
print("img height : \(fitImageView!.image!.size.height)")
imageCreated[indexPath.row] = true
}
if labelCreated[indexPath.row] == false {
// Create Label
var captionString = ""
if let mealNameText:String = meal.recipeName as String {
captionString += mealNameText
}
if let postText:String = meal.desc as String {
captionString += "\n" + postText
} else {
print("Meal Desc is null")
}
if let numForks:Int = meal.forks as Int {
captionString += "\n" + String(numForks) + " Forks"
}
if (fitImageView != nil) {
nameLabel = LayoutHelper.fitLabelBelowImageView(captionString, imageView: fitImageView!, cell: thisCell, yOffset: 0)
thisCell.addSubview(nameLabel)
thisCell.labelHeight = nameLabel.frame.height
}
}
}
return thisCell
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let thisCell = cellArray[indexPath.row]
var thisHt:CGFloat = 0
if thisCell != nil {
thisHt = thisCell!.labelHeight + thisCell!.imageHeight
}
return thisHt
}
}
Upvotes: 0
Views: 1688
Reputation: 3501
When you re-use cells (which is a good thing to do!) you must re-initialize the entire cell every time cellForRowAtIndexPath
is called. This is because it is possible that when you make this call:
tableView.dequeueReusableCellWithIdentifier
it could be returning a cell that you have already initialized. For example, let's say I have 4 rows visible on the screen:
Row 1 - Cell A [Image 1]
Row 2 - Cell B [Image 2]
Row 3 - Cell C [Image 3]
Row 4 - Cell D [Image 4]
Now, let's say I scroll down and Row 1 is no longer on the screen, but Row 5 has now appeared. It is possible that the cell you receive for Row 5 is the same exact cell that was at Row 1. So, immediately after the call to tableView.dequeueReusableCellWithIdentifier
Row 5 looks like this:
Row 2 - Cell B [Image 2]
Row 3 - Cell C [Image 3]
Row 4 - Cell D [Image 4]
Row 5 - Cell A [Image 1] <--
You need to update Cell A to point to Image 5. That may work fine in your code and you get this:
Row 2 - Cell B [Image 2]
Row 3 - Cell C [Image 3]
Row 4 - Cell D [Image 4]
Row 5 - Cell A [Image 5] <--
Now, let's say you scroll back up and Row 1 is assigned Cell A again:
Row 1 - Cell A [Image 5] <--
Row 2 - Cell B [Image 2]
Row 3 - Cell C [Image 3]
Row 4 - Cell D [Image 4]
Since you are storing arrays that keep track of which rows have been initialized (imageCreated
, labelCreated
) the cell won't get updated to point to Image 1:
// THIS WILL BE TRUE, SO IT WILL NOT EXECUTE
if imageCreated[indexPath.row] == false
This means get rid of your imageCreated
and labelCreated
arrays and perform the code to initialize your cells every time cellForRowAtIndexPath
is called.
Also, I would recommend getting rid of the cellArray
and not storing references to cells (for the same reasons I mentioned above). You can cache cell heights and that should be fine because those are not tied to re-usable cells. You could do something like this:
var cellHeightArray = [Int:CGFloat]()
Since you are doing calculations in cellForRowAtIndexPath
set the height at the bottom:
cellHeightArray[indexPath.row] = thisCell!.labelHeight + thisCell!.imageHeight;
Then set it from the array in heightForRowAtIndexPath
:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let thisCellHeight = cellHeightArray[indexPath.row]
var thisHt:CGFloat = 0
if thisCellHeight != nil {
thisHt = thisCellHeight
}
return thisHt
}
By default, however I believe heightForRowAtIndexPath
is called before cellForRowAtIndexPath
. In that case some of your cells will have a height of 0 until you reload the tableview. Look at this post for more information:
Why does heightForRowAtIndexPath: come before cellForRowAtIndexPath:?
Upvotes: 2