Using storyboard, static cells, in cellForRowAtIndexPath: the line
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
always returns nil.
I have checked the following:
Identifier of the cell is correctly set in IB/Storyboard and I use the same identifier in code. I verified this many times.
I have not instantiated the view controller elsewhere (which was the problem in this stackoverflow question).
My view controller is a subclass of UITableViewController, of course, wrapped into an ad hoc navigation controller in storyboard. Suspecting that my view controller somehow does not know about the cell identifiers defined in storyboard because it might be another instance, here is the code the "instantiates" it. In prepareForSegue:, I use
CustomViewController *vc = [[[segue destinationViewController]
viewControllers] objectAtIndex:0];
Other customizations of the view controller done here (setting properties etc.) works fine.
I am using static cells because the number of sections and rows does not change, and each cell contains static text (and other controls or text fields to be edited).
It seems to me this is a very common task (customize static cells from storyboard in the view controller's datasource methods). What am I doing wrong?
With static content in a table view, you do not implement any of the datasource methods (including tableView:cellForRowAtIndexPath:, so you would never dequeue the cells. There is no dequeuing for static content (that you can get involved in, anyway).
If you want to get a pointer to a particular cell:
get it from the table view using cellForRowAtIndexPath::
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
have an outlet to the specific cell and customise it directly.
Iterate through the cells and check the reuseIdentifier property to get the cell you are interested in.
Any of these things can be done in viewWillAppear or similar.
If you want to have completely different content in your cells to that found on the storyboard then static cells probably aren't the right choice. You should use dynamic prototypes (note you can have multiple prototypes in the storyboard) with the traditional data source methods instead.
You can still use dataSource/delegate methods of static UITableView, you just don't have to create new cells.
If you want to modify cells with dataSource methods, inside cellForRowAtIndexPath: :
UITableViewCell * cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
and then start modifying cell.
The solution was to use prototype cells rather than static cells. I am still giving the check to #jrturton as he was the first who got me the idea.
Another interesting error I just solved: with prototype cells of type "Custom", if you try to fill cell.textLabel with text, it will just automatically work, but all your other subviews behave very strangely. I just use my custom label now, and everything works fine.
Cheers, thanks everyone for helping out.
Different to the answer above,
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
will not work. But the mentioned method to create an outlet to the cell itself is working.
It is also possible to place views such as UIButtons or UITextFields on the cell and have outlets for those as well.
Both methods can also be used in combination. E.g. set the cell.textLabel.text for a particular cell and have another control which will be accessed from the controls outlet.
In the storyboard, the Static Cells CAN'T implement the Methods in the <UITableViewDataSource> protocol.
So you could use the methods which ones are include in <UITableViewDelegate>.
I know this is a very old question, but the better way to handle this is to use tableView(_:willDisplay:forRowAt:)
Related
I have custom cell created with nib. In the table view I am using the method -(float)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath to set the height of cell
Everything works fine.
But the problem is that I also want to change the size of UIlabel which is added as subview in nib of cell.
How do I do that?
Which metod to override in customcell class ?
The method you are looking for is:
-(void)layoutSubviews
{
[super layoutSubview];
//Do your magic
}
layoutSubviews is call after the cell is created and whenever the device orientation changes, to allow you to resize and/or move the subviews (plus make any other minor adjustments) to compensate for differences between the orientations, but in this case you can also use it to redraw your subviews.
As you have UILabel in your custom cell class, make a function in that class that take frame you want to set as parameter. Set frame of label in that function. You need to call this function from your cellForRowAtIndexPath method before returning the cell.
If you are reusing your custom cell you should call method only when the (cell == nil)
Also if you can add some code in your question that would be helpful and you can get more precise answer.
You need to treat your custom cell in the same way you would treat a normal view or view controller class with a xib. i.e. You need to create IBOutlets for the controls in you custom cell and during creation of the cell you can access the controls quite easily.
myCell.myCustomUILabel.text = #"blah"
There are some gotchas when using custom cells in a xib and onnecting up the IBOutlets. This SO answer (of mine) explains how to create and link up IBOutlets of a custom cell.
I've got a UITextView inside a UITableViewCell subclass. I have no problem getting the new height of the Text view and cell. The problem I have is telling the UITableView to update.
I have implemented heightForRowAtIndexPath: to return the live height of the cell as the TextView expands.
But somewhere `[tableView beginUpdates]; [tableView endUpdates]; must be called.
How? Should I add a delegate property to the UITableViewCell which is set to the UITableViewController subclass? And then send a delegate message when the cell expands height and the Tableview needs to update? It seems a little weird to have a delegate between the UITableViewCell and Controller?
I tried using NSNotificationCenter, but I have more than one editable cell, and more than tableview of this nature. So there is no way to register only for notifications for the cells without copying and pasting the same line over again, which isn't nice (as the cells are created in IB, and are not in an array or set), and having multiple tableviews means an exception occurs on the other table view as it is told to update but nothing changes.
I've seen lots of questions and answers on this topic, but when it comes to updating the tableview they all just say "now update the tableview" and not how to. So how do I telly he tableview to update, from one of it's cells?
I would think that this behavior would be best implemented in the UITableViewController instead of the view itself (the UITableViewCell).
Your controller is responsible for setting cell height, and typically will be the delegate for your UITextView's, so let it handle all of this.
In your textViewDidChange method, figure out what the new height of your cell should be, update your data structure to reflect that, and call reloadRowsAtIndexPaths:withRowAnimation: to have it actually change.
Edit:
So since you didn't like my first suggestion, another way to do this would be to add a recommendedRowHeight property to your custom UITableViewCell.
Then, you can either observe this property from your UITableViewController or implement a delegate protocol with a method along the lines of:
- (void)recommendedRowHeightDidChange
// or
- (void)recommendedRowHeightDidChangeTo:(CGFloat)newHeight
Then, when your height changes, update your recommendedRowHeight property and call your delegate's method if you go that route.
Either way, once your controller figures out that the recommended row height of a cell has changed, it can do what it is supposed to do. Update your data structures reflecting the current row heights and then call reloadRowsAtIndexPaths:withRowAnimation:.
You can add your tableview controller object as a weak reference to your tableview cell class. And in tableview controller you can have a method which will be called from tableview cell class.
I want to add a UIPickerView to a UITableViewCell. Right now I am just getting a black window. Can someone also explain me the concept of subclassing UI objects to a cell ie:when do we make our cell as the delegate and the datasource delegate? Thanks
EDIT: Here is the code of what i am doing right now
case 1: {
cell = [tableView dequeueReusableCellWithIdentifier:#"groups"];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"groups"];
}
/*NSString *myGroupDetail = [_groupArray objectAtIndex:indexPath.row];
NSLog(#"the group detail label is %#",myGroupDetail);
cell.textLabel.text = myGroupDetail;
*/
[cell addSubview:_groupPicker];
break;
}
The groups is the section in which I want the picker view, I am getting that data from an array.
you would have to allocate and initialize a pickerview in your cellForRow method of the tableviewdelegate. ill sketch it for you =) how to initialize a cell itself should not be hard to find out if you google a bit ;-)
...(tableView *)... cellForRowAtIndexPath... {
if(indexPath.row == pickerRow){
UIPickerView *pickerView = [[UIPickerView alloc]init];
cell = ... // alloc and initialize a cell
cell addSubview:pickerView];
}
else{ // your other cells }
return cell;
}
UPDATE: im currently having trouble with git, so i uploaded a sample project to my private server: UITablePicker example
github: https://github.com/sebamisc/UItableViewWithPicker
yu can modify and use the code however you want =)
sebastian
Well, I never did exactly that.
Does it have to be in a cell or could you use the table's header or footer view for that? (I would not suggest a section header/footer view.)
Assuming it is within a UITableViewCell.
Yes, I personally would subclass UITableViewCell. Did that a lot. In that case you cell object could be the data source delegate of the picker. For doing so your subclass of UITableViewCell (let's assume you name it MyTableViewCell) needs to fulfil the related protocol. You add that UIPickerView programmatically within the init Method (initWithStyle) of MyTableViewCell. For the layout within the table cell, you should overwrite the method layoutSubviews. If your app can be rotated to landscape and portrait orientations and/or if your app is designed to run on iPad as well, then this method should dynamically consider the table's bounds. (Screen or windwo bounds are often used hiere but that is not save when the table is displayed within a split master view or a popup view on iPads.)
Strictly spoken your MyTableViewCell should not be the data source delegate, simply because it is a view element and view objects are not supposed to manage any business logic within an MVC design pattern. Smarter would be the implementation of some dedicated view controller for your table view cell hat fulfills the protocol and is assigned as the delegate. However, both would work. In the event that it is the only picker view within your sell, then you could easily use your UITableViewController subclass even without tagging the UIPickerView.
An alternative to subclassing a UITableViewCell is to create the UIPickerView within the cellForRowAtIndexPath method of your tableViewController. That is fine for singe-orientation apps. However, you may setup it in a way that it re-arranges its the UIPickerView automatically.
In any case you should overwrite the heightForRowAtIndexPath method of UITableViewController when your table views do not have the same hight any more. If all of them still have the same height, then you can simply set the rowHeight property of your UITableView.
I need a way of setting the UITableViewCellAccessory for any row. However the catch is that I need to be able to do it OUTSIDE of the UITableView delegate methods.
I have tried this, but it doesn't show up the accessory.
[[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathWithIndex:1]] setAccessoryType:UITableViewCellAccessoryCheckmark];
If it makes any difference I created the UITableView in IB in a storyboard. The data is static, and I'm using a grouped table style with only one section.
Please can someone help me out?
If your table view is scrolled so the cell in question may sometimes scroll out of view and then back into view, you should manage the content of that cell only from within the UITableView method cellForRowAtIndexPath:. The reason is that when cells are redrawn, the tableview object calls this method to make sure that visible cells are properly rendered. (Cell that are not visible don't need to be rendered at all.)
That being said, this is where you should handle the cell content, even if the table view doesn't scroll the cell in question out of view. It wil lmake you life a lot easier if you follow this design pattern when working with table views.
Inside that method, you can test (using if statement, for example) the value of the indexPath.section and indexPath.row so that you can configure the specific cell the way you want it. This includes putting in the accessory.
Always use [NSIndexPath indexPathForRow:inSection:] when working with table views.
You can figure out what indexPath you need the checkmark on and then use something like this
UITableViewCell * cell = [tableView cellForRowAtIndexPath:someIndexPath];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
My UITableViewCells can accept input of data. While working in one cell, it would be perfectly natural for the user to want to scroll up, check something in another cell, then return to the first cell and continue the data entry.
The problem is that this will frequently cause the UITableView to recycle the cell, which wreaks havoc in my program.
Is there any way to temporarily tell iOS not to recycle the cell?
Once the data entry is done, I am fine if it is recycled.
I should add that the data entry uses a custom keyboard. So first responder status is not an issue.
Give a different cellIdentifier to the cells that are significantly different. If the cell at the bottom has its own identifier, then when the user scrolls to the bottom, it won't recycle your cell from the top. However, this will still preserve your cell at the top in the reuse queue, so that when you scroll back to the top, you won't need to recreate it.
There's no way how to prevent cell recycling if you do use dequeueReusableCellWithIdentifier: method of UITableView in a common way.
If you would like to do this, you should implement it on your own in UITableView's data source protocol method - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath.
As Chiefly Izzy said, it's not a common way to work. And going against the way the lists work may cause problems. If you want to reduce your problem, keep the content of your cell in memory and use this saved content to refill the cell when willDisplayCell is called instead of rebuilding the whole thing from start each time.
Gendolkari's solution gave me an idea for temporarily removing a cell from the dequeue cycle. It does involve setting a read-only value using KVC, so it's not for the truly hack-adverse.
When you first want to remove the cell from the cycle, change the reuseIdentifier for that particular cell. We want to be able to find the cell later so rename it something that we can index against. I found [NSIndexPath description] to be useful here. The reuseIdentifier is a read-only property, but this is Objective-C, and we have KVC:
[cell setValue:[[self.tableView indexPathForCell:cell] description] forKey:#"reuseIdentifier"];
Now whenever we reuse a cell in the cellForRowAtIndexPath:
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:#"cellIdentifier"];
we won't get back this particular cell because we have changed the reuseIdentifier.
However, if we scroll down then scroll back up, we probably want to display the original cell that we removed from the dequeue cycle. To do this when we are reusing a cell first attempt to dequeue a cell based on indexPath, and if we can't find that attempt to dequeue a cell simply based on the basic cell identifier string:
UITableViewCell *cell;
cell = [self.tableView dequeueReusableCellWithIdentifier:[indexPath description]];
if (!cell) cell = [self.tableView dequeueReusableCellWithIdentifier:#"cellIdentifier"];
When you want to put that cell back into the dequeue cycle, simply change the reuseIdentifier back to the original value:
[cell setValue:#"cellIdentifier" forKey:#"reuseIdentifier"];