Prevent reuse of custom cells in table view Swift - ios

I have a problem which i'm not sure how to solve.
I have two custom cell nibs - data for both is fetched from separate arrays.
The structure is the following
nib1-cell line1
nib1-cell line2
...
nib1-cell line n
nib2-cell line1
there is always one nib2-cell at the end with the uibutton.
Once the uibutton is pressed - the nib1 array is appended.
I figured out a way how to insert values at the bottom of the tableview, but when i scroll downwards or upwards the cell with nib2 is reused and replaced with nib1-cell data.
How can i either prevent those cells from being reused or save their state ?
Thank you.
UPDATE: datasource and cellForRowAtIndexPath code
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return someTagsArray.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if(indexPath.row < someTagsArray.count - 1){
var cell:TblCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as! TblCell
cell.lblCarName.text = someTagsArray[indexPath.row]
return cell
} else if (indexPath.row == someTagsArray.count - 1){
var celle:vwAnswers = self.tableView.dequeueReusableCellWithIdentifier("cell2") as! vwAnswers
celle.Answer1.setTitle(answersdict[answersdict.endIndex - 2], forState:UIControlState.Normal)
answertitle1 = "\(celle.Answer1.currentTitle!)"
celle.Answer2.setTitle(answersdict.last, forState:UIControlState.Normal)
answertitle2 = "\(celle.Answer2.currentTitle!)"
//println(answertitle2)
return celle
} else {
var cell2:TblCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as! TblCell
return cell2
}
}

You have to determine which type of cell you want in cellForRowAtIndexPath and dequeue the correct reusable cell. Maybe something like if (indexPath.row + 1)%3 == 0 then dequeue an answer cell.
However, you may possibly want to look in to using a section header for this instead. Hard to say without seeing how you implement your data source.

Related

Variable use of multiple custom cells

I'm using a unclickable tableView to display different information of one object.
For this informations I have different custom cell types one where I placed a map, if my object have locations, one have a list with links, and another a multiple line label for a little description...for example.
I manage this cells with:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell: mapCell = tableView.dequeueReusableCellWithIdentifier("mapCell") as! MapCell
return cell
} else if indexPath.row == 1 {
let cell: textCell = tableView.dequeueReusableCellWithIdentifier("textCell") as! TextCell
return cell
} else if indexPath.row == 2 {
let cell: listCell = tableView.dequeueReusableCellWithIdentifier("listCell") as! ListCell
return cell
}
}
So far so good, everything working fine. My problem is, not every object needs a map, some of them just need some text and a list, other objects need a map and a list, other all of them. I want my tableView to skip some cells if there is a condition.
I know, I can make an symbolic array for changing the number of cells of my tableView, but that deleting just from the end of my tableView, not specific cells.
One of my ideas is to generate a empty cell, maybe with a height of 0 or 1 so that I can do something like this:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row == 0 {
if mapCellNeeded {
let cell: mapCell = tableView.dequeueReusableCellWithIdentifier("mapCell") as! mapCell
} else {
let cell: emptyCell = tableView.dequeueReusableCellWithIdentifier("emptyCell") as! EmptyCell
}
return cell
} else if indexPath.row == 1 {
...
}...
}
put I don't know if there isn't an efficient way. Hope you guys can help me.
Your solution would work. Another approach (very nice and swifty) would be not to hardcode row numbers, but rather use enum instead:
enum InfoCellType {
case Map
case Text
case Links
}
...
var rows = [InfoCellType]()
...
// when you know what should be there or not
func constructRows() {
if (mapCellNeeded) {
rows.append(InfoCellType.Map)
}
rows.append(InfoCellType.Text)
... etc
}
Then in the table view methods just see what's the type for current indexPath:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellType: InfoCellType = self.rows[indexPath.row]
switch cellType {
case .Map:
let cell: mapCell = tableView.dequeueReusableCellWithIdentifier("mapCell") as! mapCell
return cell
case .Text:
...
case.Links:
...
}
}
This solution also allows to easily change order of rows - just change the order of items in rows array.

When UITableView Scrolling Returned Wrong IndexPath.Row Value

This is my codes;
// MARK: - Table View Delegate && Data Source Methods
// **************************************************
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let index = indexPath.row
print(index)
if index == 0 {
let cell = tableView.dequeueReusableCellWithIdentifier("HeaderCell", forIndexPath: indexPath)
cell.backgroundColor = ColorHelper.getCellBackgroundColor()
return cell
}
else {
if let cell = tableView.dequeueReusableCellWithIdentifier("GradeCell", forIndexPath: indexPath) as? GradeCell {
cell.activeViewController = self;
cell.gradeButton.tag = index
cell.creditButton.tag = index
cell.lessonNameTextField.tag = index
cell.lessonNameTextField.delegate = self
cell.backgroundColor = ColorHelper.getCellBackgroundColor()
return cell
}
return UITableViewCell()
}
}
I have 11 cells and someone are missing, When i scrolled table view index returns like this;
0-1-2-3-4-5-6-7-8-0-1-2..
After reload process, my values are confused. Wrong value in wrong cell, how can i fix this ?
Your problem could possible be how many cells you are returning. Especially, if you are having problems with the last one or two. From what you said, it sounds like you have 11 cells, make sure you return 12. The cells in a UITableView always start counting with 0 being the first cell and 10 being that last cell, in your case.
func tableView(tableView: UITableView, numberOfRowsInSection section:Int) -> Int {
return 12
}

Selecting a cell in UITableView another one gets affected

When I select a cell in an UITableView I change its image, but a cell out of sight (need to scroll down to see the affected cell) gets affected which means that the affected cell also change its image. It must be something about the reused cells, but I can't figured out why and how to solve this problem. Hope you guys can help me, thank you.
Another thing is, the cell should not reset its image when it get scroll out of sight.
Here is my delegates for the UITableView:
//UITableview delegate
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Arecipe.IngredientArr.count
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if (tableView == IngrediensTableView) {
print("Cell")
var cell = tableView.dequeueReusableCellWithIdentifier("Cell")
let ingredient = self.Arecipe.IngredientArr[indexPath.row]
if ((cell == nil)) {
cell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "Cell")
cell!.imageView?.image = UIImage(named: "Ingr_Uncheck")
}
cell!.textLabel?.text = ingredient.UnitValue + " " + ingredient.UnitName + " " + ingredient.Name
cell!.selectionStyle = .None
return cell!
}
return UITableViewCell()
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print("Row \(indexPath.row) selected")
if (tableView == IngrediensTableView) {
let cell: UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
print(cell.textLabel?.text)
if(cell.imageView?.image == UIImage(named: "Ingr_Uncheck")) {
cell.imageView?.image = UIImage(named: "Ingr_Check")
} else {
cell.imageView?.image = UIImage(named: "Ingr_Uncheck")
}
}
}
the affected cell also change its image. It must be something about the reused cells
That's right! The reason is that you set Ingr_Uncheck image only to a brand-new cell. If a cell gets reused, you skip setting the image, keeping whatever was set there previously.
The decision to set Ingr_Check vs. Ingr_Uncheck image needs to be done based on the state of your table's model.
if(myCodeThatChecksIfRowIsChecked(indexPath.row)) {
cell!.imageView?.image = UIImage(named: "Ingr_Check")
} else {
cell!.imageView?.image = UIImage(named: "Ingr_Uncheck")
}
Now the proper image is set to both new and reused cells, making sure that reusing a previously checked cell does not change the visuals.
The code myCodeThatChecksIfRowIsChecked needs to rely on some stored state that you change inside your didSelectRowAtIndexPath method. This is where you need to maintain a list of row numbers for cells that have been checked. This is also the list that you consult to decide if a cell should be unchecked or not: doing it based on the image is not the correct approach.
u cant compare 2 uiimage by "=="
if(cell.imageView?.image == UIImage(named: "Ingr_Uncheck"))
use this
if( [UIImagePNGRepresentation(cell.imageView?.image) isEqual:UIImagePNGRepresentation(UIImage(named: "Ingr_Uncheck"))] == YES)
You could resolve this in two ways. Either set the image to nil cellForRow
cell.imageView?.image = nil
Or if you have custom class for the cell, implement
override func prepareForReuse() {
imageView?.image = nil
}
Reset with nil or with the default image

reloadRowsAtIndexPaths doesn't update my cell data

I have a UITableView in my ViewController.
One of the cell could be tap into another TableViewController to allow select a value.
I want to update my cell after back from the callee ViewController.
right now, i could pass back the selected value by delegate.
However, i tried following way, none of them works.
self.mainTable.reloadData()
dispatch_async(dispatch_get_main_queue()) {
self.mainTable.reloadData()
}
self.mainTable.beginUpdates()
self.mainTable.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.None)
self.mainTable.endUpdates()
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
was called and executed without error.
but the UI just doesn't change
here is the way I update value in cellForRowAtIndexPath
if let currentCell = tableView.cellForRowAtIndexPath(indexPath) as UITableViewCell! {
currentCell.textLabel?.text = address
return currentCell
}
Here is my cellForRowAtIndexPath -
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let id = "Cell"
println(indexPath)
if indexPath.row == 1 {
var cell = tableView.dequeueReusableCellWithIdentifier(id) as? UITableViewCell
if cell == nil {
cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: id)
cell?.contentMode = UIViewContentMode.Center
cell?.selectionStyle = UITableViewCellSelectionStyle.None
cell?.contentView.addSubview(mapView!)
}
return cell!
}else{
let cell = UITableViewCell()
cell.textLabel?.text = self.address
return cell
}
}
Here is the delegate method -
func passBackSelectedAddress(address: String) {
self.address = address
var indexPath = NSIndexPath(forRow: 0, inSection: 0)
self.mainTable.beginUpdates()
self.mainTable.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
self.mainTable.endUpdates()
}
My fix:
After more debug, i find the cause,
the self.address value is updated in delegate, however it roll back to previous value in cellForRowAtIndexPath.
I change the property to a static property, then resolve the problem.
I'm not sure what's wrong with instance property, and why it reverses back.
static var _address:String = ""
It seems like you're trying to grab a cell from the UITableView and then update the textLabel value that way. However, UITableView and UITableViewCell are not meant to be updated in this way. Instead, store the value of address in your class and update this value when the delegate calls back into your class. If cellForRowAtIndexPath constructs the UITableViewCell with the value of self.address, calling mainTable.reloadData() after should update the cell to the new value.
For example:
var address: String
func delegateCompleted(address: String) {
self.address = address
self.mainTable.reloadData()
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(<your identifier>)
if (indexPath == <your address cell indexPath>) {
let textLabel = <get your textLabel from the cell>
textLabel?.text = self.address
}
return cell
}
Your cellForRowAtIndexPath has some problems -
You are using the same re-use identifier for different types of cell (one with a map, one without)
When you allocate the table view cell for the other row, you don't include the re-use identifier.
You have no way of referring to the map view that you are adding after the method exits because you don't keep a reference.
If you are using a storyboard then you should create the appropriate prototype cells and subclass(es) and assign the relevant cell reuse ids. If you aren't then I suggest you create a cell subclass and register the classes against the reuse identifiers. Your cellForRowAtIndexPath will then look something like -
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var returnCell:UITableViewCell
if indexPath.row == 1 {
var myMapCell = tableView.dequeueReusableCellWithIdentifier("mapCell", forIndexPath:indexPath) as MYMapCell
myMapCell.contentMode = UIViewContentMode.Center
myMapCell.selectionStyle = UITableViewCellSelectionStyle.None
// Set the properties for a map view in the cell rather than assigning adding an existing map view
returnCell=myMapCell
}else{
returnCell = tableView.dequeueReusableCellWithIdentifier("addressCell", forIndexPath:indexPath)
returnCell.textLabel?.text = self.address
}
return returnCell;
}

How to use multiple custom cells (>1) in UITableView

I've searched for an answer to this question all over Stack Overflow and have found some useful answers but my situation is different as the number of rows in the section are to be determined from the number of items listed in an array. I'm trying to create a table that uses two custom cells. The first cell displays profile information while the second displays the news feed.
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myProfileDM.profileArray.count
//return myProfileFeedDM.profileFeedArray.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) ->
UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCellWithIdentifier("bio", forIndexPath:indexPath) as! ProfileTableViewCell
cell.followerNumber!.text = myProfileDM.profileArray[indexPath.row].followerNumberInterface
cell.followers!.text = myProfileDM.profileArray[indexPath.row].followersInterface
cell.following!.text = myProfileDM.profileArray[indexPath.row].followingInterface
cell.followingNumber!.text = myProfileDM.profileArray[indexPath.row].followingNumberInterface
return cell
}
else{
let cell = tableView.dequeueReusableCellWithIdentifier("feed", forIndexPath:indexPath) as! FeedTableViewCell
//let cell: FeedTableViewCell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "feed")
cell.profileFeedLabel!.text = myProfileFeedDM.profileFeedArray[indexPath.row].profileFeed
cell.profileDateLabel!.text = myProfileFeedDM.profileFeedArray[indexPath.row].profileDate
return cell
}
}
}
when I run the program, the first cell (with identifier-bio) is the only one that loads/shows up.
I suppose the number of rows in the section is wrong. From your variable names I suspect it should be
myProfileFeedDM.profileFeedArray.count + 1
Note that in the feed array you would have to use indexPath.row - 1 to get to the right index of your array because the first row is for the profile.
I don't see any reason from the code why it doesn't work.
Try to debug cellForRowAtIndexPath method to see what is the value of the indexPath on each call
(or just put println ("IndexPath: \(indexPath)") to your cellForIndexPath method)
PS: But as long as you need your profile cell only once - I would suggest to move ProfileCell into table's or Section's header
it would be a bit more logical I think.

Resources