I've been using the code below to access the value of a textfield in my custom UITableViewCell. Problem is, this only works for cells that are visible at the time the method is called.
NSIndexPath *path = [NSIndexPath indexPathForRow:0 inSection:3];
AppointmentNotesTableViewCell *cell = [self.tableView cellForRowAtIndexPath:path];
NSString *str = cell.notesView.text;
Does anyone have a better way to access this information, regardless of the cell being visible or not?
That is because invisible cells do not exist. Cells get reused when you scroll the table view. Only those of them, visible on the screen can actually be accessed.
All the objects that potentially displayable in the table view are normally stored in some kind of list. What you need is to access the object from the list using the index you have from #path instead of trying to access its "rendered copy" from the table view itself.
Related
I have a custom UITableViewCell. When a cell gets selected, a UILabel gets added to it. I had to use prepareForReuse for it not to get messy, like so:
- (void)prepareForReuse {
NSArray *viewsToRemove = [self.view subviews];
for (UILablel *label in viewsToRemove) {
[label removeFromSuperview];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CategorieCell *customCell = [tableView dequeueReusableCellWithIdentifier:#"cellID" forIndexPath:indexPath];
return customCell;
}
The problem is when I scroll down enough that the label is out of view, and then I scroll back up, the label isn't there anymore. The reason is obviously because when the cells get reused, I removed all the labels.
So is there a way to disable prepareForReuse (or just the code in the method) for the selected row, and how?
Cells that are scrolled away will be reused, and there's no way around it. Even if you avoid the removeFromSuperview logic, that cell will reappear at a different index path, probably not where you want it.
The way to conditionally configure cells is in cellForRowAtIndexPath. There, you can ask if the indexPath is among the table view's indexPathsOfSelectedCells. If it is, then configure it with the extra labels, or not, if not.
One way to reduce the messiness is to have those labels remain in the cell unconditionally, just setting their alphas to 0 or 1, depending on the selection state.
For example, in
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
// if you know the table has singular selection
NSIndexPath *selectedIndexPath = [tableView indexPathForSelectedRow];
BOOL rowIsSelected = [indexPath isEqual:selectedIndexPath];
// OR, for multiple select...
NSArray *selection = [tableView indexPathsForSelectedRows];
BOOL rowIsSelected = [selection containsObject:indexPath];
// now either conditionally create/destroy or show/hide the subviews
// that appear on selection (I prefer show/hide for simpler cells)...
[cell configAsSelected:rowIsSelected]; // have the custom cell do it
// in that method, or here, if you're less OO-inclined...
cell.subviewThatAppearsOnSelected.alpha = (rowIsSelected)? 1.0 : 0.0;
The larger point is, this is the suggested place to reliably configure a cell based on the model and its current position in the table
Think of table cells as dumb containers that get reused to hold different things (labels, images, buttons, etc.).
You fill the cells in cellForRowAtIndexPath.
You empty them in prepareForReuse so they can be filled again and reused.
Don't confuse these two actions.
When you fill the cell, you should be filling it from data that you have stored somewhere else - i.e. not from other cells. If you are relying on indexPathsOfSelectedCells to help you when filling your cell, you are going to have problems. Don't do this.
Typically you would have an array of objects, where each object corresponds to a cell. You have as many cells in your table as objects in the array. You might initialize the objects in your array in viewDidLoad, or pass them in from a previous view controller.
This process doesn't have to be complicated. Most cells display only a few bits of data, so your object (often called a model) doesn't have to have many properties to hold this data.
When the user selects a cell, set a "selected" property in its corresponding object to indicate this. This value stays around in the object even when the cell is scrolled off the screen and reused. That's good.
Now when the user scrolls back to the cell, you fill the cell with data from the corresponding object. Since that object has its "selected" property set, you "fill" the cell by adding the label that you want there in this case. Or if it isn't set, you don't add the label.
In prepareForReuse, always remove the label to put the cell in its empty state, ready to be refilled.
I think this must be possible but I'm wondering if there is any way I can get back the selected cell. I should mention that we are randomizing certain elements in the cellForRowAtIndexPath so I am trying to get what the random data is. Since I get back the index path like this:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
NSIndexPath *path = [self.tableView indexPathForSelectedRow];
NSLog(#"here is int: %i", path.row);
How would I go back and get the specific cell? I was thinking cellForRowAtIndexPath but would this give me back a new cell - I want to query the selected cell for content (like what content is in a label or which image is in an ImageView).
Get it like this:
UITableViewCell* cell = (UITableViewCell*)sender;
I want to get a NSString out of the table HeaderView that's on top that moment. Because I want to implent it in ScrollViewDidScroll so one label changes when the header view changes.
Didn't find any clues on the web how to do this
Thanks!
There is no predefined method by which you can get top section. There is one trick by which you can achieve it. First get indexPath for all visible cells.
NSArray *visible = [tableView indexPathsForVisibleRows];
NSIndexPath *indexpath = (NSIndexPath*)[visible objectAtIndex:0];
Now you can get section value by indexpath.section. After getting this section you can get string from your data array for this section.
I have a table view with custom cells. Cells have an image view, a label and a progress view. What I want to achieve is to update progress view in a specific cell according to the event that fires. My idea is to find the cell by name label that is in the event and then update progress view. This may not be an efficient way but I couldn't think of some other way. Other ideas are also appreciated. Thanks in advance.
You could use a NSDictionary with the name as the key and the NSIndexPath as the object. Then to can look up the index path for a given cell using the name in the dictionary.
UITableView has a 'cellForRowAtIndexPath:` method that should give you the cell for a specific row.
i.e. something like
// Get visible cell for data item
NSInteger row = [myArrayOfThings indexOfObject:thing];
NSIndexPath *inedexPath = [NSIndexPath alloc] initWithRow:row section:0];
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indeexPath];
// Update cell here ...
...
Disclaimer : Code typed from memory - you may have to look at the documentation for some of the method names :)
the more better way is with tag,
you can give each cell a unique tag and access that cell by this method
//method of UIView(UIViewHierarchy)
- (UIView *)viewWithTag:(NSInteger)tag;
// can be used like this
UITableViewCell *cell = (UITableViewCell*)[tableView viewwithtag:tag];
I have a UITableViewController with prototype cells containing UITextFields. To configure these custome cells, I've created a UITableViewCell subclass. I've conected the textField to the cell subclass via an outlet (nonatomic, weak).
On this subclass I've created a protocol for which the UITableViewController is its delegate so that everytime something changes in these textFields, the TableViewController knows about it. Basically I wanted this to save the values on the NSUserDefaults
Besides, in order to dynamically obtain values from these textFields, I can do something like this:
((TextFieldCell*)[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:2 inSection:0]]).textField.text
It works ok most of the times. However when the textField is outside of the view because it has scrolled, the vaulue I get from textField.text is (null). As soon as it gets in the view again, everything goes back to normal.
I tried to change the outlet from weak to strong but to no avail.
I guess I could define some private NSStrings on the class, and fill them out when the delegate protocol gets called. The thing is that I wanted to get my code as generic as possible, keeping the need for private variables as low as possible, mostly to simplify the cell generation code.
Is there any other way to get the values of the textFields when they are outside of the view?
Thanks in advance!
But you know that UITableView only keeps Cells for the visible rect?
When a cell leaves the screen, and a new cell is needed for another cell moving into the visible area, the old cell is reused for the new content.
So there is not one cell for each row of your table view.
And if your table contains a lot data, there are far more rows than cells.
As Thyraz said, the UITableView only keeps cells for the visible rect -- and a reasonable buffer to allow for scrolling. Thats why 'reuse identifiers' are so very important, they indicate which cells can be used for which tables (critical when you have more than one table to worry about). Unfortunately, that doesn't answer your question by itself.
The responsibility for storing the contents of those textViews isn't on the UITableView's shoulders. It's your job to provide that data through the data source delegate protocols, and therefore you should be querying the data source for that information.
Edit: Which means that yes, you should be storing this data somewhere else, usually in the form of properties on the view controller class that contains the table view. I'd recommend the use of NSArray for the purpose, but you can also do it through dicts or even, at the last resort (and this is more a in theory you can do this, but it's an incredibly bad idea kind of thing), a series of properties. Personally, I almost always use NSArrays because they're structured in a manner appropriate to the problem, but you could theoretically do it other ways. (I've used a dict based structure exactly once, and that was a situation where my data was nested inside itself in a recursive structure)
UITableViewController doesn't keep cells around once off the screen. You can use the following pattern to get a previously used one as a memory management optimization, but you MUST assume that cells need to have the values reset on them every time they come onto the screen (even if dequeued) because there is no guarantee what the values will be.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier1 = #"Cell1";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier2];
if( cell == nil ) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier1] autorelease];
cell2.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell2.editingAccessoryType = UITableViewCellAccessoryNone;
}
switch( indexPath.section ) {
case first_Section:
if( row == 0 ) {
cell1.textLabel.text = #"Some Text";
cell1.accessoryView = [self myCustomViewControl];
cell = cell1;
}
... etc
}
}