I'm trying to call cellForRowAtIndexPath from within heightForRowAtIndexPath in order to assign a height based on the cell's type (I'm subclassing UITableViewCell). Trivial, right? Well, calling it there causes a loop. I can't quite seem to figure out why that would be. Placing breakpoints in both methods doesn't yield anything—the delegate method cellForRowAtIndexPath never actually gets called. Take a look:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
switch indexPath.row {
case 0:
return SubclassCellTypeOne()
default:
return SubclassCellTypeTwo()
}
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
// Calling cellForRowAtIndexPath here causes a loop
let cell = tableView.cellForRowAtIndexPath(indexPath)!
if cell is SubclassCellTypeOne {
return UITableViewAutomaticDimension
} else {
return 100
}
}
Any idea why that's happening? And any suggestions on how to get around it? Thanks!
When a reference to a cell is made via a UITableView, (usually by iOS, when loading your view), iOS calls the methods in its lifecycle - e.g., heightForRowAtIndexPath, editingStyleForRowAtIndexPath to work out how to display it etc.
So your source of an infinite loop is that you make a reference to a cell, inside a method that is called when a reference to a cell is made ;)
To fix this, you should reference back to your data source, instead of asking the cell directly about itself. If you have a class set up as a data collection, this is easy.
Yep, you shouldn't call cellForRow inside heightForRow.
In heightForRow you have the indexPath variable. You can use indexPath.row to determine the class of the cell inside heightForRow, just like you do in cellForRow.
You could also have forgotten to set the delegate and datasource properties of the tableview. Or you are returning 0 from numberOfRowsInTable...
That could also be why you are not hitting the breakpoint inside cellForRow.
Related
I'm moving and app I had on Parse to Firebase and I ran into an issue with a cell that needs to get resized.It contains a textview that recieves data, since the text varies in size, I am using this two methods:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
With Parse this worked perfectly because I would recieve the entire object that I had to pass to the DetailTableView in the MainTableView. With Firebase, I handle it differently, I retrieve what the MainTableView needs, then I just pass a reference to the DetailTableView and again retrieve whatever I need there. The problem seems to be that the size of the cell gets set before the async function can set the text. Any ideas on how to solve this?
Thanks in advance!
I think you can wrap the code setting the text on the cell with calls to tableView.beginUpdates()
tableView.endUpdates()
I want to expand my UITableViewCells with a UITableView that has multiple sections. The way I'm doing it is as follows:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
selectedRowIndex = indexPath
habitTableView.beginUpdates()
habitTableView.endUpdates()
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if (selectedRowIndex != nil && indexPath == selectedRowIndex!){
return 147
}
return 90
}
However there are some strange behaviors, for example, if one cell expands it sort of "eats up" the next section header underneath it so the section header disappears. I am just wondering - is there any nuances with a UITableView that has multiple sections?
So, you can do one thing is to have two protocol type of UITableViewCell. One is for normal and another one is for expanded. Once you type on a cell, you just need to update the delegate to use expanded one instead normal one. When updating, you only need to call reloadRowsAtIndexPaths to prevent reload everything.
You only need to create one more cell prototype and have a boolean value for indicating the state. Then, add your logic to cellForRowAtIndexPath.
I am a new to iOS development using Swift.
I am trying to understand how functions are called in a view controller that controls a table view.
In the examples I am looking at, the view controller runs three functions, all called 'table view', and each function does something unique such as returning how many rows are in a section, or using reusable cells.
But I just can't see when or how these functions are called.
Are they called when the user navigates to the view? If so, how? And how come these different functions all have the same name (i.e. func tableView ())?
Here is some sample code:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dwarves.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier(simpleTableIdentifier)
as? UITableViewCell
if (cell == nil) {
cell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: simpleTableIdentifier)
}
cell!.textLabel.text = dwarves[indexPath.row]
cell!.textLabel.font = UIFont .boldSystemFontOfSize(15)
return cell!
}
I just can't see when or how these functions are called.
A UITableView calls these methods on its delegate or dataSource to get information about what it should display, and communicate when certain actions occur. For a UITableViewController, the controller itself is both the delegate and the data source. So you won't see these methods get called unless you set a breakpoint within them.
Are they called when the user navigates to the view? If so, how?
When the user navigates to the view, the default implementation of UITableViewController sets the table view's delegate and data source properties to self. The table view itself calls these methods lazily when it needs information to create, size, layout, and display table cells appropriately.
And how come these different functions all have the same name? I.e func tableView ()
They don't. In both Swift and Objective-C, argument names are part of the method name. For example, this method:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 80
}
is named tableView(_:heightForRowAtIndexPath:).
I have an iOS app with a lot of static cells (for a preferences view), so it makes sense to put all of that in storyboard, but I would like to be able to add a checkmark to them based on if the preference is set or not.
I have my delegate method setup
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
}
I just can't figure out how to "grab" the cell from the interface builder using the indexpath so that I can decide programmatically whether or not I should add a checkmark. I have a feeling there is some sort of superclass/delegate method I can call, but I'm not sure what it is. Thanks.
When you use static cells, you need to put them in a UITableViewController because there's magic going in there. Under the hood, it implements those data source methods for you. But you can override them. The important thing is that you need to call the super version to let it do it's job. If the method returns a value you need to return that too.
So in your case:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = super.tableView(tableView, cellForRowAtIndexPath: indexPath)
let isChecked = true // put your logic to determine whether the cell should be checked here
cell.accessoryType = isChecked ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone
// ...
return cell
}
In my code i have a table with static cell inside storyboards. I'm trying to fire a method upon clicking the last static cell.
What should i write in the code to make this happen. How can i refer static cells inside the code without firing error.
In the viewController add:
#property (nonatomic, weak) IBOutlet UITableViewCell *theStaticCell;
Connect that outlet to the cell in the storyboard.
Now in tableView:didSelectRowAtIndexPath method:
UITableViewCell *theCellClicked = [self.tableView cellForRowAtIndexPath:indexPath];
if (theCellClicked == theStaticCell) {
//Do stuff
}
With static cells, you can still implement - tableView:didSelectRowAtIndexPath: and check the indexPath. One approach, is that you define the particular indexPath with #define, and check to see whether the seleted row is at that indexPath, and if yes, call [self myMethod].
Here is my take when mixing static and dynamic cells,
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if let staticIndexPath = tableView.indexPathForCell(self.staticCell) where staticIndexPath == indexPath {
// ADD CODE HERE
}
}
this avoids creating a new cell.
We all are used to create the cell and configure it in cellForRowAtIndexPath
Following CiNN answer, this is the Swift 3 version that solves the issue.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let staticIndexPath = tableView.indexPath(for: OUTLET_TO_YOUR_CELL), staticIndexPath == indexPath {
// ADD CODE HERE
}
}
this approach allow to not necessary implement cellForRow method, specially if you are using static cells on storyboard.
I think you were having the same problem I was. I kept getting an error when overriding tableView:didSelectRowAt, and the reason was that I'm used to just calling super.tableView:didSelectRowAt, but in this case we don't want to do that. So just remove the call to the super class method to avoid the run-time error.