UITableViewController accessing static cells programmatically issue - ios

Say I have a table with 10 static cells in it, is there a way to select a certain cell programmatically?
I've tried this
UITableViewCell *cell = [self.tableView.subviews objectAtIndex:indexPath.row];
but that does not actually return a table cell it seems.
this seems to crash my code
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
Im trying to set the individual heights for the static cells in code. An option would be to make outlets for each individual static cell, but that seems silly.

To access statically created cells, try this:
UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
This works for static cells. So, if you're in the...
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
return cell;
}
... delegate, you can access all statically configured cells using the above declaration. From there, you can do what ever you want with "cell".
I had a ViewController that had two UITableViews on it. One of them had cells defined statically, with a Storyboard, and the other had cells defined dynamically using code. Given I was using the same ViewController as delegate for both tables, I needed to prevent new cells from being created where cellForRowAtIndexPath was being called where cells had already been created.
In your case, you need to gain programmatic access to your cells.
Have fun.

Create an #IBOutlet.
This will work even if you re-arrange your static cells programmatically.

You can try this...
UITableViewCell *cell = (UITableViewCell*)[yourTableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:rowvalue inSection:0]];

If you need accessing the cell object, then using UITableViewCell method cellForRowAtIndexPath is quite appropriate.
That may either just pass the cell, if it is visible, or call the delegate method cellForRowAtIndexPath (do not mix them up) which you should provide. If that one crashes then dig deeper and investigate the root cause of the crash.

Use table view delegate method
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger height;
if(0 == indexPath.row)
{
height = 44;
}
else
{
height = 50;
}
return height;
}

This is a Swift 2.3. Solution.
The UITableViewController is created in IB.
/*
NOTE
The custom static cells must be
In the IB tableview if one is being used
They also must be updated to be MYCustomTableViewCell
instead of UITableViewCell
*/
import UIKit
class MYCustomTableVC: UITableViewController
{
override func viewDidLoad()
{
super.viewDidLoad()
// NO Nib registration is needed
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = super.tableView(tableView, cellForRowAtIndexPath: indexPath) as! MYCustomTableViewCell
// MYCustomTableViewCell can be created programmatically
// without a Xib file
return cell
}
}

Related

UITableView reloadData doesnt reload the table but UITableView reloadSections work

I have a UITableView embedded inside a parent UIView. I have a CustomUITableViewController class set as delegate and datasource for the tableview.
After a certain background operation, I get an updated array of objects to be displayed in the tableview.
When I update the datasource array and call tableview.reloadData method, the tableview doesn't refresh. It only refreshes if I scroll the tableview.
However, if I call the API as follows:
tableview.beginUpdates -> tableview.reloadSections -> tableview.endUpdates,
it works perfectly and immediately reloads the table.
The problem is that depending on the new data, I have to add a new section, or remove an old section from the tableview.
Hence I am not able to use the reloadSections API.
Any thoughts on how to fix this?
Code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellID = #"tempCell";
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
if (cell == nil) {
cell = [[CustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID];
}
[cell initializeWithModel:modelsToShow[indexPath.row]];
return cell;
}
-(void) showModelsInList:(NSMutableArray*) models {
[modelsToShow removeAllObjects];
[modelsToShow addObjectsFromArray:models];
[self setupDataForList];
[self reloadTable];
}
-(void) reloadTable {
[self.tableView beginUpdates];
NSMutableIndexSet* index = [[NSMutableIndexSet alloc]init];
[index addIndex:0];
[self.tableView reloadSections:index withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
//[self.tableView reloadData]
}
The showModelsInList method is invoked from the other class, in the main thread itself.
The modern way to initialize table view cells is to register the cell class (or nib, if the cell is defined in its own nib). viewDidLoad is a good time to do this...
// if the cell is a prototype defined in the nib containing the table view, or if
// the cell is built in code in its init method
[self.tableView registerClass:[CustomCell self] forCellReuseIdentifier:#"tempCell"];
// or, if the cell is defined in its own nib
UINib *nib = [UINib nibWithNibName:#"your cell's nib name goes here" bundle:nil];
[_tableView registerNib:nib forCellReuseIdentifier:#"tempCell"];
In either case above, the cell must have it's "tempCell" identifier initialized in IB or in code. Then, in cellForRowAtIndexPath, dequeue the cell using the method...
CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:#"tempCell" forIndexPath:indexPath];
No further check is required to see if (cell == nil). This version of dequeue will just work (or crash, if something's not setup correctly).
I think, technically, it's a bug, but the truth is that, though it's not documented, you shouldn't be recreating subviews in cellForRowAtIndexPath when reusing cells.
Create the cells with all needed subviews at design time in Interface Builder. Changing their positions, sizes, and other properties in cellForRowAtIndexPath is okay.
If your cells have different subviews, each cell "type" should be its own class. Create a different prototype cell class with a different identifier for each, and simply use that identifier when you dequeue the cell. That way, you have the proper cell class in cellForRowAtIndexPath.
To reference additional properties (subviews) from your view controller, simply create class files for each cell type (derived from UITableViewCell). Assign it to the prototype UITableViewCell in IB, drag the views to the .h file to create outlets like you do for a view controller, then import that class in your view controller.
So, you might end up with code like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (whatever) {
MyBasicCell *cell = [tableView dequeueReusableCellWithIdentifier:#"basicCell"];
cell.specialLabel.Text = ...
return cell;
} else {
MyOtherCell *cell = [tableView dequeueReusableCellWithIdentifier:#"otherCell"];
cell.otherLabel.Text = ...
return cell;
}
}

Does UITableView have an array property that holds all of the cells?

In one of my methods I want to iterate between the cells I have and perform changes on them, something like:
for (UITableViewCell *cell in ___________) {
cell.accessoryType = accessoryType = UITableViewCellAccessoryCheckmark;
}
so is there a property that will complete the ____________ ?
tnx!
There are only visible cells array self.tableView.visibleCells
for (UITableViewCell *cell in self.tableView.visibleCells) {
cell.accessoryType = accessoryType = UITableViewCellAccessoryCheckmark;
}
The UITableView itself doesn't know much about its content. That's the job of it's dataSource delegate. See documentation for UITableViewDataSource
Rather than thinking of the problem in a linear fashion. Think of the problem as event driven. If you want to modify the cell before it is displayed, you can check out tableView:willDisplayCell:forRowAtIndexPath: on the UITableViewDelegate
edit: given that you are looking to set the accesoryType you should probably try using tableView:cellForRowAtIndexPath:. Since that's where you're either dequeueing a reusable cell or manually alloc] init]'ing one .
Have you tried: yourTableView.visibleCells
Chikabuz is right.
BUT you should avoid making changes to the cells like this. Instead you should tell the tableView that your cells are updated and provide the changes in - [id<UITableViewDataSource> tableView:cellForRowAtIndexPath:] like so:
- (IBAction)toggleSelecting:(id)sender {
[[self tableView] setEditing:![[self tableView] isEditing] animated:YES];
// or with custom use with own BOOL property:
// [self setEditing:![self editing]];
[[self tableView] reloadRowsAtIndexPaths:[[self tableView] indexPathsForVisibleRows] withRowAnimation:UITableViewRowAnimationFade];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// cell setup here...
// or with custom use with own BOOL property:
// if ([self editing]) {
if ([[tableView] isEditing]) {
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
}
return cell;
}
Probably you can't get all cells in one array. But you can try to use the following:
let cellsArray: []
func tableView(_ tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//Your data source methods
let cell = ......
....
cellsArray.append(cell)
}
This way you can get array of cells after view loads. I think there is no any other way to do that.

UITableViewCell and label not displaying text

I have a UITableView with a cell that has two labels in it. The table view is linked to the dataSource and delegate, the labels in the cell are linked to an IBOutlet, what not. It looks to me like this should work, but this code below is not running so the Cell or labels are not populated with text. Any ideas? Or anything I'm missing?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"theLabelCell";
CustomClassCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
CustomClassText *customText = _arrayThatHasText[indexPath.row];
if (![self isSelectionMode]) {
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
cell.TitleLabel.text = customText.firstLabel;
cell.TextLabel.text = customText.secondLabel;
return cell;
}
Did you remember to also register the cell identifier for reuse?
- (void)viewDidLoad
{
[super viewDidLoad];
[self.tableView registerNib:[UINib nibWithNibName:#"CustomClassCell" bundle:nil] forCellReuseIdentifier:#"theLabelCell"];
}
If you are not seeing any cells, then check whether numberofSections and numberOfRowsInSection delegate methods are not returning 0
When does your _arrayThatHasText get populated? The issue might very well be that the data source (_arrayThatHasText) is getting instantiated before the numberofSections and numberOfRowsInSection delegate methods are being called and then after these methods are called the data source is being populated with actual data -> which would result in the 0 values in the delegate methods as you are experiencing.
You might want to try putting a [self.tableView reloadData] call at the end of ViewWillAppear or in ViewDidAppear method and see if that helps.

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.

How does cellForRowAtIndexPath work?

I HAVE READ apple documentation and it's not understandable for such a beginner in Objective-C as me. I'm trying to implement multicolumn UITableView following this link example and it just doesn't work so i need to comprehend how cellForRowAtIndexPath work, cause for me personally this method seems pretty complicated.
1) What does it return? UITableViewCell? But why does it look so odd?
-(UITableViewCell *)tableView:(UITableView *)tableView
What is that? Could you please explain?
2) How does it get called and what is more important how am i to connect it to a certain UITableView??? What if i have two UITableView's named firstTableView and secondTableView and i want them to be different (to perform cellForRowAtIndexPath differently)? How am i supposed to link my UITableViews to this
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
the method accepts NSIndexPath, not UITableView. What am i gonna do?
I'll try and break it down (example from documention)
/*
* The cellForRowAtIndexPath takes for argument the tableView (so if the same object
* is delegate for several tableViews it can identify which one is asking for a cell),
* and an indexPath which determines which row and section the cell is returned for.
*/
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
/*
* This is an important bit, it asks the table view if it has any available cells
* already created which it is not using (if they are offScreen), so that it can
* reuse them (saving the time of alloc/init/load from xib a new cell ).
* The identifier is there to differentiate between different types of cells
* (you can display different types of cells in the same table view)
*/
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MyIdentifier"];
/*
* If the cell is nil it means no cell was available for reuse and that we should
* create a new one.
*/
if (cell == nil) {
/*
* Actually create a new cell (with an identifier so that it can be dequeued).
*/
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"MyIdentifier"] autorelease];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
/*
* Now that we have a cell we can configure it to display the data corresponding to
* this row/section
*/
NSDictionary *item = (NSDictionary *)[self.content objectAtIndex:indexPath.row];
cell.textLabel.text = [item objectForKey:#"mainTitleKey"];
cell.detailTextLabel.text = [item objectForKey:#"secondaryTitleKey"];
NSString *path = [[NSBundle mainBundle] pathForResource:[item objectForKey:#"imageKey"] ofType:#"png"];
UIImage *theImage = [UIImage imageWithContentsOfFile:path];
cell.imageView.image = theImage;
/* Now that the cell is configured we return it to the table view so that it can display it */
return cell;
}
This is a DataSource method so it will be called on whichever object has declared itself as the DataSource of the UITableView. It is called when the table view actually needs to display the cell onscreen, based on the number of rows and sections (which you specify in other DataSource methods).
1) The function returns a cell for a table view yes? So, the returned object is of type UITableViewCell. These are the objects that you see in the table's rows. This function basically returns a cell, for a table view.
But you might ask, how the function would know what cell to return for what row, which is answered in the 2nd question
2)NSIndexPath is essentially two things-
Your Section
Your row
Because your table might be divided to many sections and each with its own rows, this NSIndexPath will help you identify precisely which section and which row. They are both integers. If you're a beginner, I would say try with just one section.
It is called if you implement the UITableViewDataSource protocol in your view controller. A simpler way would be to add a UITableViewController class. I strongly recommend this because it Apple has some code written for you to easily implement the functions that can describe a table. Anyway, if you choose to implement this protocol yourself, you need to create a UITableViewCell object and return it for whatever row. Have a look at its class reference to understand re-usablity because the cells that are displayed in the table view are reused again and again(this is a very efficient design btw).
As for when you have two table views, look at the method. The table view is passed to it, so you should not have a problem with respect to that.
Basically it's designing your cell, The cellforrowatindexpath is called for each cell and the cell number is found by indexpath.row and section number by indexpath.section . Here you can use a label, button or textfied image anything that you want which are updated for all rows in the table.
Answer for second question
In cell for row at index path use an if statement
In Objective C
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *CellIdentifier = #"CellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(tableView == firstTableView)
{
//code for first table view
[cell.contentView addSubview: someView];
}
if(tableview == secondTableView)
{
//code for secondTableView
[cell.contentView addSubview: someView];
}
return cell;
}
In Swift 3.0
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell:UITableViewCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as UITableViewCell!
if(tableView == firstTableView) {
//code for first table view
}
if(tableview == secondTableView) {
//code for secondTableView
}
return cell
}

Resources