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

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.

Related

Is cellForRowAtIndexPath called when you use a custom cell?

I am using a custom cell class in a tableview controller.
When I include a statement in the tableviewcontroller in cellForRowAtIndexPath NSLog(#"method called"): it does not seem to get called.
Is it possible that this method is not called when you have a custom cell?
Edit:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"cell for row at index path called");
NSDictionary *item= [self.getItems objectAtIndex:indexPath.row];
//This sets place in storyboard VC
IDTVCell *cell = [self.tableView dequeueReusableCellWithIdentifier:#"Cell"];
cell.item = item;
if (cell == nil) {
cell = [[IDTVCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"Cell"];
}
return cell;
}
cellForRowAtIndexPath is not called if no rows are returned.
-tableView:cellForRowAtIndexPath: is not getting called
That is what happened in my case.
It can also not get returned if you reload table on wrong thread and in certain other scenarios.
cellForRowAtIndexPath: not called
However, a custom cell per se does not cause this..
To answer your question - Yes, it is.
There could be n-number of reasons why cellForRowAtIndexPath: is not getting called. This may be because delegate / dataSource is not set or UITableView frame is not set... etc. etc.
You should easily find a solution with more online research and closure look at your code.

didDeselectRowAtIndexPath issue

I have a tableview in a scrollview in a popover. When the view is presented, the bottom cell in tableview is not visible to the user. If I select all of the cells then deselect the fist cell, the out of view cell is deselected too. Has anyone come across this behaviour before? If so, how to approach it?
Now your job is to find all the visible cells in the tableview and then apply select/deselect to it.
UITableView *tableView = self.tableView;
// Or however you get your table view
NSArray *paths = [tableView indexPathsForVisibleRows];
// For getting the cells themselves
NSMutableSet *visibleCells = [[NSMutableSet alloc] init];
for (NSIndexPath *path in paths)
{
[visibleCells addObject:[tableView cellForRowAtIndexPath:path]];
}
// Now visibleCells contains all of the cells you care about.
-(void) tableView:(UITableView *)tableView didDeselectRowAtIndexPath:
(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:
(NSIndexPath *)indexPath {
//stuff
//as last line:
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
For that matter, deselectRowAtIndexPath can be called from anywhere at any time you want the row to be deselected.
[self.myTableView deselectRowAtIndexPath:[self.myTableView
indexPathForSelectedRow] animated: YES];
If you are using dequeueReusableCellWithIdentifier: change your cellForRowAtIndexPath: to use dequeueReusableCellWithIdentifier:forIndexPath:
In a UITableView cells get reused. That means it only produces as many as absolutely needed. As soon as a new one is coming onto the screen, the last one is "recycled" instead of initialising a whole new instance.
This makes your application run faster. It also means that you have to undo any changes you made, when recycling.
Selection status is one of them. The UITableView should manage this automatically for you, if it is dequeued with the relevant indexPath. If not, it wouldn't know whether that specific cell should be selected.

How to Initialize my UITableView table to have a certain number of rows based on input from user and a Button

I'm really new to Objective-C here so what I'm asking may be trivial to most of you but any guidance will help.
Here's a picture of my storyboad.
My current objective is to allow for the user to enter in the number of sets (NSInteger *numReps) and then press the "Log Reps" button and have the table initialize with numReps cells that look like the prototype cell.
Now where I'm at a loss for the implementation. I've never done this kind of thing before so I'm not exactly sure what the best way to go about it is. I have thought of making a custom class for the UITableView table that would take info from the view after the Log Reps button is pushed. I'm not entirely sure how this would need to be implemented. Or can I simply add the table to the properties of the view controller and setup the table within the view controller? That was my initial idea and seems ideal so that I would have everything in one place.
Pleas advise. I am new to all of this and come from a C++ background so I'm still learning a lot of the notation.
Try this:
-(IBAction)btnLogClicked {
int iSet = 4 //Number of row in table
UITableView *tblView= [[UITab;eView alloc]initWithFrame:CGRectMake(0,50,320,100)];
tblView.delegate = self;
tblView.dataSource = self;
[self.view addSubView:tblView];
}
Table View Data Source and Delegate Methods
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return iSet;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// Display what you want to display in each cell
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
It's not clear what you want to present in your prototype cells, but you need an array (or probably an array of dictionaries) to populate the cells. The number of rows is determined by the number of entires (the count) of that array. So, if you take the number entered by the user, and add that many object to your array, then call reloadData on the table, you will get the number of rows that you want. What those object are that you add to the array, depends on what you're trying to show there.
you could start reading: Table View Programming Guide for iOS
But I can answer you:
You can add the UITableView to the UIViewController, but you need set your UIViewController like the TableView's delegate and dataSource. Your ViewController need to implement the protocol: UITableViewDataSource
The method that you are looking for is: tableView:numberOfRowsInSection:
But I really recommend you that read the Apple Reference.

UITextField within UITableViewCell -- programmatically make it firstResponder when it comes on screen?

I have a UITableView with a UITextField inside of each cell. A model object that stores the index of the cell that is currently being edited. If the cell scrolls off-screen, my app takes away first-responder status. (Failing to do so may cause problems). Now, suppose a cell (possibly the same one, or possibly a different one) corresponding to that index is about to scroll back onto the screen. I want to make that cell's textField the firstResponder. My delegate does receive a call
tableView: willDisplayCell: forRowAtIndexPath:
corresponding to the new cell. However, calling becomeFirstResponder: at that point does not help as the cell won't accept firstResponder status until it has been displayed.
Short of using a timer, any ideas for how to call becomeFirstResponder: at a point when the cell is in fact able to become the first responder?
EDIT: cellForRowAtIndexPath: is always called before willDisplayCell:. So no help there.
I haven't tried this, but the first thing I'd try is in cellForRowAtIndexPath...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// standard stuff to build cell and setup it's state
if ([indexPath isEqual:self.myModel.indexPathOfTextFieldBeingEdited]) {
// you probably have a handle to the text field from the setup above
UITextField *textField = (UITextField *)[cell viewWithTag:SOME_TAG];
[textField performSelector:#selector(becomeFirstResponder) withObject:nil afterDelay:0.0];
}
return cell;
}
You have to show cell on the screen to make it as first responder. Do at first:
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:NO];
and then call first responder on it's label/textField.
Here's what I did in MonoTouch - it's important that you do not animate the ScrollToRow() - i.e. "animated:NO" as shown in the answer by edzio27 (thanks edzio27 :) ).
var newCell = (UIOrderLineCell)tableView.CellAt(newIndexPath);
if (newCell == null)
{
tableView.ScrollToRow(newIndexPath, UITableViewScrollPosition.Middle, false);
newCell = (UIOrderLineCell)tableView.CellAt(newIndexPath);
}

segue not working with UITableViewCell alloc, but dequeueReusableCellWithIdentifier

I'm using storyboard with UITableView in UINavigationController.
In this UITableView, used custom tableViewCell having interior properties.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CustomTableViewCell *cell = nil;
if (SYSTEM_VERSION_LESS_THAN(#"6.0") ) {
//iOS 6.0 below
cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
}
else {
//iOS 6.0 above
cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath]; //work segue
}
Above code work well with push segue. But not when I used
cell = [[CustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell"]; //not work segue
I used this alloc method for preserve cell's data from reusing cell.
It's just alloc vs deque.. method difference. What am I missing?
edit) I know that not using the dequeReusableCell method is bad for the performance reason. But, the number of cells would not be many. This is why I don't need the deque method.
"not working" means "do not perform push segue", not crash.
It shows cell same like when dequeReusable method used except the disclosure indicator icon at the right of cell. The indicator icon come from storyboard setting.
And when I touch the cell, the cell highlighted blue but the push segue does not performed.
CustomTableViewCell has 4 properties. That's all different from UITableViewCell. Users set the properties at DetailViewController(push segue lead to this). The cell doesn't have IBOutlet ref. In MasterViewController(having the tableView), cellForRowAtIndexPath method returns CustomTableViewCell above code.
cellForRowAtIndexPath method adds a on/off button on the left of indicator on CustomTableViewCell
And set a tag number for the cell.
The use of dequeueReusableCellWithIdentifier is what enables you to use your prototype cell. If you use initWithStyle instead of dequeueReusableCellWithIdentifier, then you don't and you therefore lose any segues, disclosure indicators, other UI appearance that you've defined for those cell prototypes, too.
If you're determined to go this route, you'll have to go "old school" (i.e. do what we all used to do before cell prototypes) and write your own didSelectRowForIndexPath. But if you already have that segue defined, let's say you called it "SelectRow", then your didSelectRowForIndexPath can perform that:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
[self performSegueWithIdentifier:#"SelectRow" sender:cell];
}
If you need to have your disclosure indicator, then your custom cell routine (or the cellForRowAtIndexPath) will have to set that manually. And if you add it with
cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
Then you need to manually handle it:
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
[self performSegueWithIdentifier:#"SelectAccessory" sender:cell];
}
Bottom line, you can get this to work, but you're just doing a lot of extra work and losing losing the performance and memory benefits of dequeuing cells. I'd heartily encourage you to revisit the decision to not use dequeueCellWithIdentifier.

Resources