Should I set the cell on cellForItemAtIndexPath or willDisplayCell? - ios

this page that talks about tricks to smooth table view scrolling, says the table view cells should be drawn not on tableView:cellForRowAtIndexPath: but rather on tableView:willDisplayCell:forRowAtIndexPath:.
I have tried that approach on and I see no difference on my initial tests. Is there any truth about that?
should I really use tableView:willDisplayCell:forRowAtIndexPath: to create the cell, set thumbnails, texts, etc. instead of tableView:cellForRowAtIndexPath:?

Yes its true that both perform the same function but use cellForRowAtIndexPath for tableview where datasource has to be implemented as cellForRowAtIndexPath works fast and you must return reused cell identifiers quickly. This is a method of datasource of tableview
tableView:willDisplayCell:forRowAtIndexPath is a method of delegate & not datasource. This method is called exactly before loading cells in UITableView bounds.

Related

cellForRowAtIndexPath is being called for non visible cells also

tableview data source method cellForRowAtIndexPath is being called for the non visible rows also which is creating a problem in pagination in tableview. Did anyone face this issue?
The system calls this method to prepare cells ahead of time, so scrolling is smoother and more responsive. If you want to fetch more data as the user scrolls down/up you should rather implement UITableViewDelegate method tableView(_:willDisplay:forRowAt:) - this method is called just before the cell is displayed to the user.

In UITableView, what's the delegate for "visibleCells"?

When cells come in and out of device's screen, I want my viewController to know exactly what came and what went out. Is there a way to do this?
There isn't a delegate method just for "visible cells". There isn't anything called when a cell leaves the screen. There really isn't anything when a cell becomes visible.
There is the cellForRowAtIndexPath data source method. This is called when a cell is needed.
There is the willDisplayRowAtIndexPath delegate method. This is called when a cell will be displayed.
There is the didEndDisplayingCell delegate method. This is called when a cell is removed from the table view.
There is the indexPathsForVisibleRows method on UITableView. This lets you know what rows are currently in view.
There is the prepareForReuse method on UITableViewCell. This lets a cell reset itself to be reused for another row.
Better describe what you are trying to accomplish in order to get a more specific answer.

iOS8 change height of dynamic cell "now", re internal cell content

Here's a dynamic cell
Note - in the example, the text is not data driven. It's just some text local to the cell (consider, say, a help text). At runtime, change the .text of the UILabel from one word to many lines, using a button actually inside the cell. iOS perectly resizes the cell and table....
... but only when the cell is scrolled offscreen, and then on again.
How to alert the table view to recalculate everything "now" ?
(Please note, this question ONLY in the case of iOS8+, Xcode7+, autolayout for dynamic cell heights.)
Changing height
So basically, there are two ways to do:
The first one is to actually reload the cell (not the tableview). Reloading will call new heightForRow (don't forget to purge cache, if you are caching the sizes), which will return proper new height:
let indexPaths = [NSIndexPath(forRow: ~the rows in question~, inSection: 0)]
self.table.reloadRowsAtIndexPaths(indexPaths, withRowAnimation: .Automatic)
(Note however that this often involves reloading more than one row; notably if you select/deselect, you have to reload all rows changed.)
If however you ONLY want to change the size of the cell and the content per se, and did not really change the data content ... so for example:
you clicked some button and you assigned new local text in the cell to outlets (perhaps a help text):
you changed only the LAYOUT of the cell. for example, you made a font larger, or changed the margin of a block of text so that the height of a block of text changed, so indeed the height of the overall cell changed:
In that case instead of reloading, just call the following, which forces the tableview to basically do all animations, and for that it needs new heights, so it requests it:
self.table.beginUpdates()
self.table.endUpdates()
The true solution
I see what your problem is. You are trying to change the height of the cell from the actual cell - but you will not succeed in that -> and you should not. See, the cell is view, and view should not have any idea about its data whatsoever - view is what presents. If you need any changes, you should inform your controller to do so. To do that, you can use notifications, but preferably protocols / delegates.
So at first you create protocol in your cell, which will be used to inform the controller, that there is a change:
protocol MyCellDelegate {
func buttonTappedForCell(cell : UITableViewCell)
}
Now, you need to conform to that protocol in your view controller that contains table:
class MyClassWithTableView : MyCellDelegate
Lastly, you need to declare delegate in the cell:
class MyCell {
var delegate : MyCellDelegate
}
And assign it in the configuration of the cell, which you probably have in the view controller:
cell.delegate = self
This is the basic setup for all the delegates / protocols really, and now, when you click on your button, you can forward the action to your controller:
#IBAction myButtonTouchUpInside() {
self.delegate.buttonTappedForCell(self)
}
After doing all that, proceed as in part 1. That is to say, either reloadRowsAtIndexPaths or a beginUpdates / endUpdates pair as explained above.
Hope it helps!
I'm presuming you're not setting the text property of the UILabel inside cellForRowAtIndexPath but rather somewhere else (or doing it asynchronously). If that's the case, I wouldn't update the UI there. Rather, I'd update the model backing the table and then call reloadRowsAtIndexPaths. That will let cellForRowAtIndexPath call again, but unlike reloading the whole table, this will gracefully keep the contentOffset of the tableview right where it is.
I know this all sounds unnecessarily complicated, but the bottom line is that you don't own this view, the table view does. It has to do all sorts of stuff above and beyond updating the cell. I.e., if the cell grew, figure out which cells scrolled out of view and dequeue them. If the cell shrunk, figure out which cells scrolled into view as a result.
It's a surprisingly complex dance. You can try calling setNeedsLayout on the cell, but I wouldn't expect that to work (and even if it does, it is a fragile approach). The table view is responsible for managing its cells, so if you really should just update model and reload that one cell.
did you try calling reloadRowsAtIndexPaths on the cell index? it's supposed to animate to the new size, if the constraints are setup correctly.
You should call self.tableView.reloadData() just AFTER you made the cell's label's text change.
It will force the tableView to redraw the cell's. That's what happened when you scroll, the cell is being reused, and redrawn when it comes back again.
EDIT:
If you can't or won't do a reloadData on your tableView, you can use:
self.tableView.beginUpdates()
self.tableView.reloadRowsAtIndexPaths([NSIndexPath(row:0 section:0)] withRowAnimation:UITableViewRowAnimation.Automatic)
self.tableView.endUpdates()
I dont know your code but did you really execute your ui changes on the main thread. Same problem happened to me and was solved with putting the exectuion on the main thread.
dispatch_async(dispatch_get_main_queue()) { () -> Void in
[...]
}

Multiple cells got affected after calling cellForRowAtIndexPath

I have a UITableView, and I want to change text color of the selected row. But I see every other 15 row got affected along with the one I clicked. This is tested under the master-detail sample project.
if let indexPath = self.tableView.indexPathForSelectedRow(){
self.tableView.cellForRowAtIndexPath(indexPath)?.textLabel?.textColor = UIColor.redColor()
}
I checked cellForRowAtIndexPath value in debug session, it seems returning only one object, how come other cells got affected too?
Cells are reused - almost certainly you are not resetting your cells to a base state in prepareForReuse and configuring them correctly on each call to cellForRowAtIndexPath.
When you look at a table view that can display some number of cells at once, typically there will only exist one more cell than can be shown. When a cell is moved off the screen it is placed in a pool of cells for reuse. Just before a cell moves onto the screen it is configured by you, in cellForRowAtIndexPath. If you have configured something in the cell and you do not configure that explicitly every time you return a cell from cellForRowAtIndexPath then that configuration persists in the cell that is in the reuse pool. The function prepareForReuse is also called before each cell is reused - if you have subclassed UITableViewCell then you can implement this function to return the cell to a base configuration, so that settings like text color do not unexpectedly affect multiple cells.
This approach makes it possible to scroll through an entire table view smoothly with a minimum amount of memory used. It is an expensive operation to create and destroy cells every time one disappears and a new one is required.
The simplest fix is to always set the text color in cellForRowAtIndexPath - either to the base color or to the special color you want in some cells.

Knowing when a UITableView is done loading its cells

The purpose of this question is not to know when a UITableView is done loading its data (which has been answered in this post) but to know when a UITableView has done drawing all its cells.
Using the visibleCells property inside the
- (UITableViewCell *)tableView:(UITableView *)iTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
method doesn't work because when the tableview is loaded for the first time, no cells are visible yet.
The idea is to animate the cell appearance when the tableview is refreshed.
This seems like a complex task to perform but I'm surprised Apple didn't provide any delegate method to use when a tableview is done loading.
Better try this method, pass NSArray of indexPaths that you want to reload with given set of animation
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
Hope it works
I don't believe there is a method that indicates when a table has loaded it's cells. However, you can achieve your goal of animating in the table cell whenever it is drawn.
Logic:
UITableView displays UITableViewCells as needed.
Each UITableViewCell is a subclass of UIView.
UIView has drawRect, which is called every time the view is drawn.
So, put your animation in UITableViewCell's drawRect method & it'll animated whenever it's drawn.
Implementation:
Create a subclass of UITableViewCell,
Apply this subclass to your table cells,
Add the drawRect method, and
Implement your animation in the drawRect method.
I have done this in one of my projects and it worked great. It draws the animation when the table is loaded or scrolled and when the display is rotated. With this approach you don't need to know when the table is done loading – each cell is simply animated whenever it is drawn.
One final note – if you put another view controller on top of your table view and then dismiss it, you may need to reload the table and set each UITableViewCell to setNeedsDisplay. This will force drawRect to be called again for each UITableViewCell.

Resources