How tableView:cellForRowAtIndexPath: is supposed to be used? - ios

The master-detail project created by XCode wizard contains implementation of tableView:cellForRowAtIndexPath: that calls dequeueReusableCellWithIdentifier:forIndexPath:, then calls own custom method configureCell to fill the cell controls with valid data, and then returns the cell to caller. As I understand, this is how table knows how to draw its cells content.
What I don't understand is how to use it when I want to get cell from my code. And is it even supposed to be called by my code, or is it only a callback used by table framework itself?
For example, if I just want to change text of one label in cell, I thought I can just call tableView:cellForRowAtIndexPath:, and change what I need in the resulting cell object controls. But calling tableView:cellForRowAtIndexPath: actually makes a new cell object (probably reusing one from pool of unused cell objects) and fill ALL controls like imageviews and labels in it with data. This does not look good to me regarding to performance, when I want change just one label.
So maybe I could remove configureCell out of tableView:cellForRowAtIndexPath:? But then how to ensure all cell contents will be redrawn by system when [table reloadData] is called?
P.S.
XCode 7 wizard created this kind of code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
NSManagedObject *object = [[self fetchedResultsController] objectAtIndexPath:indexPath];
[self configureCell:cell withObject:object];
return cell;
}
// Then later in controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] withObject:anObject];
break;
configureCell is called twice here. Seems like non-sense to me. I hoped at least people who write code for wizards understand how this is supposed to be working.

There's two different methods being called here that have similar names.
tableView:cellForRowAtIndexPath: is a UITableViewDataSource method that you implement to create the cell for the table to show. The tableview calls this method when a new cell is going to come on screen.
controller:didChange:... is calling a different method called cellForRowAtIndexPath: which is a UITableView method. This method queries the table for a cell that is already being displayed after previously being created using the tableView:cellForRowAtIndexPath: datasource method and doesn't result in it being called again. It's calling this method to access the already displayed cell and update it with new info when the object changes.
Both calls to configureCell are necessary.

Related

Why is awakeFromNib called twice from a Cell in a TableView?

I'm trying to understand why awakeFromNib is being called twice in my code. I currently have a tableview that has a special compressible cell that appears once at the end of the table. The first awakeFromNib is being called when the tableview is scrolled to the special cell at the end (which is fine I believe,as the tableview is reusing cells). However, whenever I tap the cell to expand the cell, the awakeFromNib is being called again.
Could anyone explain to me why awakeFromNib is being called twice? And how I could only make it only be called once?
Thanks
EDIT** Code people have requested
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section >= (NSInteger)[self.trip.destinations count]) {
GuestCell *cell = (GuestCell*)[tableView dequeueReusableCellWithIdentifier:GuestCellIdentifier forIndexPath:indexPath];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
[cell setupCellForGuests:self.trip.guests];
cell.guestExpanded = NO;
NSLog(#"RETURNING CELL");
return cell;
}
// For all other sections
return [self prepareCardCellForIndexPath:indexPath forHeightCalc:NO];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section >= (NSInteger)[self.trip.destinations count]) {
[self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
}
You're animating the reload of the expanding row. The table view implements this by creating another cell for the same index path, and animating a transition from the old cell to the new cell. It creates a second instance of your cell prototype, so the second instance also receives the awakeFromNib message. If you log self, you'll see that the address is different the second time.
I don't think you can avoid the creation of a second cell instance (and thus a second awakeFromNib) unless you get rid of the animation. Even then I'm not sure it will reuse the old cell.
If the cell with that nib is only one in the table, then my guess is that it has something to do with animations. I didn't check how tableview handles cells during animation, but for tableview header it asks for another instance and then performs animation (for example fade) - so the old instance is faded out and the new is faded in. At least that's what I think has the highest probability, if you are handling cells correctly.

Configuring custom UITableViewCell

In my project I'm creating custom cells by subclassing UITableViewCell. When cellForRowAtIndexPath: is fired I do a pretty basic stuff like:
MyCustomCell *cell = [self.tableView dequeueReusableCellWithIdentifier:[MyCustomCell identifier]];
I don't want to manually configure cell properties in cellForRowAtIndexPath: so I thought I'd create a method inside MyCustomCell called configureWithModel: which is filling MyCustomCell with proper data. So far, so good! Now inside cellForRowAtIndexPath: I have something like:
MyCustomCell *cell = [self.tableView dequeueReusableCellWithIdentifier:[MyCustomCell identifier]];
[cell configureWithModel:model];
In configureWithModel: I assign some data (image also) to cell so as you'd guess it could be slow'n'heavy so I wonder if this is a good solution to have a method like this in subclass of MyCustomCell? What is more, how it's related to prepareForReuse?
Doing this [cell configureWithModel:model]; is the best approach because take for a case when you want to use configureWithModel: in more than 2 tableViews you can avoid code redundancy and cell level control would be there with cell itself.
Use of [cell configureWithModel:model]; will make your code look like more structured, but for image use the following delegate
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
Example :
- (void)tableView:(UITableView *)tableView willDisplayCell:(AlbumCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
AlbumBO *album = [self.arrAlbums objectAtIndex:indexPath.row];
dispatch_async(imageQueue_, ^{
UIImage *image = [self retrieveImageWithImageUrl:album.coverPhoto];
dispatch_async(dispatch_get_main_queue(), ^{
[cell.imgVwAlbum setImage:image];
});
});
}
Here
AlbumCell is my Custom table cell
AlbumBO is the object for containing image object
And
[self retrieveImageWithImageUrl:album.coverPhoto]
is the user defined method to download image.
This sounds like a fairly decent usage of the singular responsibility principle. Where this might bite you is if your cells need to be binded with images that must be downloaded from a server. In this instance you don't want your cell responsible for triggering a download since the cell will then also be responsible for monitoring the progress of the download. Since these cells are reusable this becomes more problematic as the cell becomes reused.
So yes, in a simple case where you need to bind data to a cell it makes sense for the cell to be responsible for configuring its subviews with the relevant data.
Regarding prepareForReuse a casual glance at the documentation details
Discussion If a UITableViewCell object is reusable—that is, it has a
reuse identifier—this method is invoked just before the object is
returned from the UITableView method
dequeueReusableCellWithIdentifier:. For performance reasons, you
should only reset attributes of the cell that are not related to
content, for example, alpha, editing, and selection state. The table
view's delegate in tableView:cellForRowAtIndexPath: should always
reset all content when reusing a cell. If the cell object does not
have an associated reuse identifier, this method is not called. If you
override this method, you must be sure to invoke the superclass
implementation.

Capture cell being pressed without didSelectRowAtIndexPath

I have the following UITableViewCell (well, subclassed).
With didSelectRowAtIndexPath it is possible to capture that a cell has been selected in UITableViewController. My problem occurs due to the fact that directly pressing Choose User bypasses the selection of the cell.
How could I allow my UITableViewController to be aware that UITableViewCell foo has been pressed even if the user immediately hits Choose User?
N.B. I don't need the Selection capability per se, this was just by method of knowing that a user had tapped within a cell area.
You could just call the method directly. If we say that for each Choose User button we are setting the row number as the tag and assuming that you don't have sections so everything will happen in section 0 we could do.
- (void)hitChooseUser:(id)sender
{
// Do whatever you want for when a user hits the `Choose User` button
// Code......
// Then do this at the end or whenever you want to do it.
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[sender tag] inSection:0];
// Also assuming you have created you have created you UITableView correctly.
[self tableView:myTableView didSelectRowAtIndexPath:indexPath];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Do whatever it is you want.
}
I also found this link that may help you Manually call didSelectRowatIndexPath
You could also disable the user interaction with the cell itself by setting userInteractionEnabled: to NO for each cell in cellForRowAtIndexPath: so didSelectRowAtIndexPath: will only get called when you want to call it manually.
Do not call didSelectRowAtIndexPath: It is a UITableViewDelegate method and, where possible, should be used as such (meaning let the UITableView send messages to it). In addition, it creates an unnecessary dependency on UITableView implementation.
That being said, in order to achieve shared behavior that is performed either on button click, or on row selection, refactor it out into a common method that is not coupled with UITableViewDelegate
For example:
-(void)doSomethingCommon {
//do shared code here
}
-(void)chooseUserButtonPressed:(id)sender {
[self doSomethingCommon];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[self doSomethingCommon];
}
And if your UITableView shows more than one of these rows, for which you depend on knowing which corresponding model object is related to the cell, than you can use the tag property on UIView subclasses (usually something in your cell) to mark the row that the object is shown in.

iOS UITableView reload

I ran into a piece of code for UITableView, click on of the cell and it reloads but what exactly is the data source? I am not too clear on this.
[xTable reloadRowsAtIndexPaths:[NSArray arrayWithObjects:[NSIndexPath indexPathForRow:button.tag inSection:0],nil] withRowAnimation:UITableViewRowAnimationFade];
It runs the following "tableview cellForRowAtIndexPath" method and loads the updated information for the specified cell.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
Hope it helps.
DataSource is a protocol in the UITableView that defines a set of messages that the UITableView sends to this object(datasource)
These messages returns data that is filled inside the tableview
Some messages are like:
numberOfRowsInSection: you will return the number of rows this table will have
cellForRowAtIndex: you will return here the UITableViewCell object for the given index
numberOfSectionsInTableView: you will return the number of sections this table will have
How this works
in your class you will call tableView.dataSource = self;
[UITableView reloadData] gets executed, the datasource messages get sent to the datasource object (in this example it will be the object set on the right side of .dataSource = self;
table view will use these consecutive calls to build the table view itself
For further indepth reading please rever to
Simple tutorial on how to implement the datasource in iphone
Data Source design pattern

UITableView programmatically on runtime

I want to do the following:
I have to create a UITableView at the runtime depending upon some conditions, at the run time only I will come to know from which database table the data has to be pulled from to be presented in the UITableView to make this problem more complicated I will have to create a custom UITableViewCell also at the run time.
I am not able to think how do I create this UITableView and then how do I create all those delegate method at the runtime.
I will give some more background that will help understand this problem, I am making a request to my server, and the server returns me an xml response object, after parsing I figure out that I have to present a table to the user on a particular action and the table will have custom cell, for which the values are available in the xml response object.
Please help I have been trying to figure out this thing for a while now.
EDIT:
I will try to explain in a different way, if that helps people understand my problem.
Hi, I want, to do the following:
On the runtime(which means while my app is running) I have to create a UITableView depending upon some conditions(some action taken by the user), I will make a server call and will get the data for the UITableView.
There has to be a a custom UITableViewCell for this UITableView that I have created at the run time, I will get the information for the custom UiTableViewCell also at the run time.
So basically I don't have any thing at the compile time except that I may have to create a UITableView and a custom UITableViewCell.
I am trying to figure out that how do I create the delegate method and custom UITableViewCell at the run time.
One thing that I thought was to have a default class with all the delegate method and when I create UITableView at the run time associate this class as the delegate for the newly created UITableView, let me know if this is an ok solution.
But how do I associate the custom UITableViewCell to this delegate method at the run time is still an issue.
You just need to get the new values that you would come to know during the run time and then use [tableViewObject reloadData];
The delegate functions will remain the same. In IB just place the UITableView wherever you want and set the delegate and datasource to the file owner. Set the hidden property to yes by checking the check box.
Once the user does some action change the hidden property in the action function as tableViewObject,hidden = NO;
Your delegate methods will look like
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [tableData count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath (NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"acell"];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:#"acell"] autorelease];
}
cell.textLabel.text = [tableData objectAtIndex:indexPath.row];
return cell;
}
In this tableData will be your datasource that could be declared in your .h file. In the function that captures the action of the user, you can get the data from the server and add it to the tableData and then as suggested earlier call [tableViewObject reloadData];
After you are done parsing the reply from the server you should call
[tableView reloadData];
And in the method [tableView:cellForRowAtIndexPath:]
Depending upon some values or identifiers that you have for each database load values from that particular database and render it into tableView cell.

Resources