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.
Related
What is the best way of preventing the user from selecting a cell inside a UITableView, but allowing my program to call selectRowAtIndexPath: on the table view?
I also want the controls in the UITableViewCell to remain interactive (i.e. allow touchesBegan: to be called on the UITableViewCell).
If I do [tableView setAllowsSelection:NO], calling selectRowAtIndexPath: does not do anything.
I realized that when you call selectRowAtIndexPath: programmatically, then the delegate method willSelectRowAtIndexPath: will not be called. However, if a user taps a cell, then this will be called. This method can return nil to prevent selection.
- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
return nil;
}
This will ensure that only programmatic calls to selectRowAtIndexPath: will select a row and any taps to select a row will not.
Uncheck the box : User Interaction Enabled in TableView as well as TableViewCell
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.
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.
I am attempting to select, in the view did appear method, a table cell programatically.
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:1 inSection:1];
[self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
my delegate,
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
does not get called.
The delegate gets called if i select the cells in the simulator with a mouse, however, just not programmatically.
Why can this be?
Because that's how the framework works. Try reading the docs. That's what they are for!
selectRowAtIndexPath:animated:scrollPosition:
Selects a row in the receiver identified by index path, optionally scrolling the row to a location in the receiver... Calling this method does not cause the delegate to receive a tableView:willSelectRowAtIndexPath: or tableView:didSelectRowAtIndexPath: message.
That's pretty easy to understand. The delegate method didSelectRowAtIndexPath: is not called if you select the row in code (programmatically). It is called only if the user selects the row.
And this makes perfect sense, because:
If you are selecting the row in code, you might not want the delegate method triggered.
If you do want the delegate method triggered, since you are in your own code, you can just call it.
You don't need the delegate method in order to learn that the row was selected, because you selected it in code - you cannot not know!
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.