My TableView has been repeating the first cell and I had trouble figuring out the problem. I've searched around here and found a relevant thread (UITableView Cells All Repeat 1st Cell).
I figured out that the problem was that I was not specifying the section in my cellForRowAtIndexPath method. I understand that I have to implement indexPath.section into my code, but I can't figure out how/where.
Any help is greatly appreciated!
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cellIdentifier = "Cell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath:indexPath) as CustomTableViewCell
// Configure the cell...
let entry = entries[indexPath.row]
cell.entryTextView!.text = entry.bodyText
cell.entryDayLabel!.text = entry.day
cell.entryDateLabel!.text = entry.date
return cell
}
Related
I created two ViewControllers and two TableViews. Then i added prototype cell to one TableView, set it up according to my needs, copied it to the other TableView, changed its class and identifier and linked it up in ViewController that is datasource and delegate for each one.
The problem is, FEEDING one is behaving good, having constraints as expected, and the WALKING one is not, but i have no idea why since they have all same properties in each one's:
ViewControllers:
FEEDING
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = myFeedingTableView.dequeueReusableCellWithIdentifier("feedingcell", forIndexPath: indexPath) as! FeedingCell
cell.time.text = self.vremena[indexPath.row]
return cell
}
WALKING
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = myWalkingTableView.dequeueReusableCellWithIdentifier("walkingcell", forIndexPath: indexPath) as! WalkingCell
cell.time.text = self.vremena[indexPath.row]
return cell
}
CustomCell files
each one is connected to its class
FeedingCell is class of feeding prototype cell
WalkingCell is class of feeding prototype cell
Constraints
and the constraints are same, as you can see on the picture.
Here is the image providing different results and constraints:
image
Solved by changing rowHeight settings in TableView. Thanks #SilentLupin
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;
}
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
I have multiple UICollectionViewCells. When the user taps on a specific cell, I would like my app to change the background image of the touched cell.
My approach is to focus on the didSelectItemAtIndexPath method. When a cell is touched, this method will be called.
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
var cell: UICollectionViewCell = collectionView.dequeueReusableCellWithReuseIdentifier("cellIdentifier", forIndexPath: indexPath) as! UICollectionViewCell
cell.backgroundView = UIImageView(image: UIImage(named: "myImage"))
}
However, I can't get it working but I don't know why. The issue is probably related to indexPath, that doesn't return a correct value of the cell. I tried using indexPath.row and this does actually return an Int number of the cell.
What's more, I'm also creating a new UICollectionViewCell with var but this cell already exists.
Why isn't the cell updating its background image? How do I change the background image of a UICollectionViewCell that has been touched by the user?
I totally agree with the Josh's answer, but if you change the background image using the didSelectItemAtIndexPath method it works fine as well. Then, you can use the cellForRowAtIndexPath method that returns the UITableViewCell at the specified indexPath, like in the following way:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var cell = tableView.cellForRowAtIndexPath(indexPath) as UITableViewCell!
cell.backgroundView = UIImageView(image: UIImage(named: "photo2"))
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! UITableViewCell
cell.backgroundView = UIImageView(image: UIImage(named: "photo1"))
cell.selectionStyle = .None
return cell
}
I just put the selectionStyle to .None to avoid the highlight. I hope this help you.
Using Swift: I've hooked up an NSFetchedResultsController to a UITableView, and created a subclass of UITableViewCells for the TableView cells.
The UITableViewCells format and display correctly. But the app crashes as soon as I try editing the rows (adding or deleting).
For testing, I've used the most basic subclass possible: An empty subclass, and it still crashes. The original UITableViewCell works normally.
Any ideas? Swift bug? The crash goes straight to the top of the stack. I get nothing in the console.
Works:
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell? {
var cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
let task = fetchedResultController.objectAtIndexPath(indexPath) as Tasks
cell.textLabel.text = task.desc
return cell
}
Doesn't work:
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell? {
var cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell2
let task = fetchedResultController.objectAtIndexPath(indexPath) as Tasks
cell.textLabel.text = task.desc
return cell
}
When subclass is:
import UIKit
class UITableViewCell2: UITableViewCell {
}
If you create table cells programmatically (not in InterfaceBuilder) you have to register the table cell class with
TableView.registerClass(..., forCellReuseIdentifier...)