I have UITableView which cells contain one UITextField in it. My UITableViewController is a delegate of all this text fields. Now, when UITableViewController gets deallocated, I want to set delegate of all that text fields to nil. The text field has a tag, so I can get it by it's tag once I have a cell.
The question is how to get all created cells? Asking UITableView for visibleCells returns only visible cells, but it can happen, that there is a row which is not visible, bit it still has my UIViewController as a delegate. So I really need to get all created cells somehow. cellForRowAtIndexPath does the same, so it wouldn't work for me either. The only way I see here is to store all text fields in array, but may be there is a better way?
Here is some code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = (UITableViewCell*)[tableView dequeueReusableCellWithIdentifier:#"reuseId"];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"reuseId"];
UITextField *nameTextField = [[UITextField alloc] initWithFrame:nameTextFieldRect];
nameTextField.tag = TEXT_FIELD_TAG;
nameTextField.delegate = self;
[cell.contentView addSubview:nameTextField];
}
return cell;
}
-(void)dealloc
{
// todo: get all text fields and set theirs delegate to nil
}
Well, most answers suggest that I don't need to set delegate to nil, but as I'm paranoid, I suspect that the following scenario is possible:
User taps 'Back' button, so dealloc of my view controller is called. In dealloc my view controller releases it's table view, but the tableView still exists at this point, as well as all the text fields. And if somehow one of text fields would call it's delegate method at this point, the app would crash, because the delegate is not a valid object anymore.
If someone can explain why this scenario is not possible, than it would convince me that I don't need to set delegate to nil.
You do not need to do that. All of the cells will be deallocated as well, so they won't have a reference to the delegate.
By default the delegate is a weak reference, so it will not retain your object.
I am not expecting to have this answer marked as accepted, but this won't fit into a comment window.
So, rather than us trying to convince you, you could start one of the most important tool for an iOS developer, which is the profiler. And see for yourself by playing with the interface that you should get no more than the number of cells necessary to fill the screen are kept allocated, and then when you tap back, all are getting deallocated.
If they are not, they probably have a strong reference to something else, but this can easily detected with the profiler.
I also like to add that when working with cells the act of scrolling UITable, tap back enter again into table, scroll tap back (repeated at least 10 times) it's a mandatory practice to detect memory leak.
Also, I don't know the purpose of assigning a tag to the cell, I maybe wrong but with this:
nameTextField.tag = TEXT_FIELD_TAG;
consider that you have more than one cell with the same tag, therefore you cannot simply recall the desired one. I remember that the rule is the first placed on screen 'win' the TAG (or kind of).
[UPDATE]
Just a guess, I have never proved this, but to stay on the question, if your problem is to have a cell first for having the UITextView, have you tried to loop the main view and just ignore the cell:
UITextView textView = [mainView viewWithTag:TEXT_FIELD_TAG];
while(textView!=nil){ // or whatever loop or criteria you like
// deallo, nil, etc...
textView = [mainView viewWithTag: TEXT_FIELD_TAG];
}
Create a new delegate/protocol for that cell, and implement that delegate in the view controller, like PersonTableViewCellDelegate. Apart from that, your cell implements the UITextField delegate and in that code, call [self.delegate onKeyPressed] or whatever. I recommend you pass also a reference of the cell, so in the view controller you can use indexPathFromCell (or something like that) to know the position of the cell. If you are interested tell me about it and will copy some code.
Related
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (tableView == sessionListTable){
selectedSessionId = [[self.sessionNames objectAtIndex:indexPath.row] objectForKey:#"session_id"];
selectedSessionName = [[self.sessionNames objectAtIndex:indexPath.row] objectForKey:#"session_name"];
NSLog(#"%#",selectedSessionId);
[StudentCommon sharedInstance].mTeacherSessionId = selectedSessionId;
[self remoteSessionButtonClick];
currentIndexPath = indexPath;
previousIndexPath = currentIndexPath;
[sessionListTable reloadData];
[sessionListPopUp removeFromSuperview];
}
TheUILabel` inside table view is not responding to touch, though the touch is working only in Empty space of the cell.
Verified all the connections, constraints and classes.
Check all the properties of inside UI elements in Attribute Inspector,
make sure for all of them are user interaction is “Ticked” ☑️
Check the properties inside User Defined Runtime Attributes and remove which are
not required.
Check the Class name whether it’s the correct “Super Class” / “Relevant Custom
Class” or not.
Check inside Connections Inspector whether the connection is having any warning ⚠️, if warning exists remove connection and connect again.
This is the image showing the User Defined Runtime Attributes, which was causing this error
First you should know that UILabel doesn't respond to TouchUpInside event.
Apart from that, the events those are raised in UITableViewCell, is not catchable in superview (i.e UITableViewController or UIViewController that is hosting respective table).
In order to achieve your goal, you need to change your UILabel to a UIButton. And in order to escalate TouchUpInside event to super view, you have two options:
1- Create a delegate for your cell, and utilise it in your superview.
2- Post a notification from your cell, and observe (catch) it in super view.
Good Luck. :-)
I'd like to know when a UICollectionViewCell is displayed on the actual screen. cellForRowAtIndexPath is insufficient, as the cell isn't actually seen at this point. didEndDisplayingCell is insufficient, as it gets called when the cell is removed from view.
UITableViewDelegate has a willDisplayCell method that I've found useful for similar stuff in the past, but it doesn't appear to exist in UICollectionViewDelegate.
How can I determine when the cell is brought on to the screen?
Make a subclass of UICollectionViewCell. Override its didMoveToWindow method:
- (void)didMoveToWindow {
if (self.window != nil) {
// I am now in the window's view hierarchy and thus “on screen”.
}
}
Technically, the cell could still be not visible, either because it's outside the window's visible bounds, or because it's covered by another view. But normally neither of those will be the case.
Note also that if a cell is reused, it might not get removed from and re-added to the view hierarchy. The collection view can just change the cell's frame. (I know UITableView does this with table view cells as of iOS 6.0.) In that case, you won't receive a didMoveToWindow message when the cell gets re-used for another item.
If you explain why you want to know when a cell is displayed, we might be able to give you a better answer.
-[UICollectionView visibleCells] returns a NSArray of all cells visible on screnn
I have a UITableView and each UITableViewCell contains an editable UITextView.
My data source is a NSMutableArray containing NSMutableDictionary that holds the text value and some styling keys for the text.
How can I (efficiently) make it so that any changes a user makes to the UITextView are updated in the corresponding datasource NSMutableDictionary?
A rather simple way is to utilize the index path of the table, it is NOT the cleanest so it depends on the complexity of your datasource, and if you have multiple tables etc.
What you can do is when the user ends editing the textView or selects another row in tableView, you read the indexPath of the selected row (That requires that the row keeps actually being in the selected state while editing the textView which it should by default). From there you call your update method.
To catch the end of editing you implement
-(void)textViewDidEndEditing:(UITextView *)textView
{
NSIndexPath *selectedpath = [myTable indexPathForSelectedRow];
[self myUpdateMethodForIndexPath:selectedpath];
}
To catch deselect of the table row and the above doesnt get called, you implement
-(void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self myUpdateMethodForIndexPath:indexPath];
}
Your update method must then read the value of the textView at the corresponding cell at the indexPath and handle this in the datasource. To care for sections of course you need to correctly handle the indexPath, in the example just the row is used (1 section).
-(void)myUpdateMethodForIndexPath:(NSIndexPath *)editPath
{
UITableViewCell *editCell = [myTable cellForRowAtIndexPath:editPath];
NSString *newText = editCell.theTextView.text;
....
NSMutableDictionary *dict = [myDictArray objectAtIndex:editPath.row];
....
}
First of all, you must assign a tag to each UITextView, to know exactly which UITextView are you refering.
Then you must implement UITextViewDelegate in your view controller which holds the tableview. Then, make this view controller the delegate of each UITextView. Read here how to implement it: UITextViewDelegate reference.
Look for the protocol method that better fits your needs (probably – textView:shouldChangeTextInRange:replacementText:, wich is called each time the text changes in any range.
In the delegate method, you can read the text with UITextView.text property, and assign this value to your data model (the dictionary).
Another possible approach is to use KVO pattern, but it requires more coding and a better understanding both, the pattern and the implementation. Hope it helps!
Make your view controller the delegate of each text view. Listen for appropriate events to get the updated text. Then have the view controller update the data model with the updated text.
If you have custom cells then have the cell be the text view delegate. Then the cell should notify its delegate (the view controller) about the updated text. Of course this requires that your custom cell class define its own delegate protocol and the view controller should make itself the delegate of each cell.
That's as specific as an answer can be for such a vague question.
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
}
}
I have a ViewController that contains a UITableView in which the cells are created via a custom subview (so that each cell will have a label and textfield). The subview includes TextField delegate methods that get fired on textFieldDidBeginEditing, textFieldDidEndEditing, etc. The UITableView caches the cells that are visible and destroys them when a cell is no longer visible. The problem I have is when the user taps on a TextField in a cell and then scrolls the table so that the cell being "edited" is out of view and then taps on a new cell to edit it, the original cell's textFieldDidEndEditing delegate method is called, causing a "respondsToSelector:]: message sent to deallocated instance" error because the original/first TextField has been destroyed by the UITableView when it was scrolled out of the visible area.
Has anyone dealt with this issue before and found a solution so that I can keep my textFieldDidEndEditing methods for proper handing of the data the user's input without having the now-invisible cells destroyed?
EDIT: I just found this in the UITableView Class Reference doc: "Avoid relayout of content. When reusing cells with custom subviews, refrain from laying out those subviews each time the table view requests a cell. Lay out the subviews once, when the cell is created."
This is, I think, the root of my issue. But I am not sure how to follow this advice. Help...
"The UITableView caches the cells that are visible and destroys them when a cell is no longer visible." That's true only if you don't provide a reuse identifier.
Assuming that you're working on a detail view where each cell is presenting (conceptually at least) a property of some model object, you can assign each cell a unique reuse identifier. You can set this directly in Interface Builder, or, if you're creating the cells programmatically, by passing it as an argument to initWithStyle:reuseIdentifier: or initWithFrame:reuseIdentifier.
Given a reuse identifier, the table view will cache the cells for its entire lifetime. To get a cell from the cache use
cell = [tableView dequeueReusableCellWithIdentifier:#"Some Identifier You Made Up"];
Again, use different identifiers to distinguish unique cells, if necessary.
An alternative for a detail view that has a fixed number of unique cells is to store a reference to each cell in an instance variable (or store the whole group of them in a collection). As long as you retain the instance variables, the cells won't be deallocated even if you didn't bother providing reuse identifiers.
Im wondering if this could be your issue:
if (cell == nil) {
// dont do this -->cell = [[[customCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
NSArray *topLevelsObjects = [[NSBundle mainBundle] loadNibNamed:#"NewUserCustomCell" owner:nil options:nil];
for (id currentObject in topLevelsObjects){
if ([currentObject isKindOfClass:[UITableViewCell class]]){
cell = (customCell *) currentObject;
break;
}
}
I don't know if you found a solution yet. But I faced the same problem, and I fixed it by setting the delegate of the textfield to nil in dealloc of my custom field, before releasing it. This way the delegate textFieldDidEndEditing is not called on the deallocated cell.
I was also not using a reuse-identifier.
- (void)dealloc {
[textField_ setDelegate:nil];
[textField_ release];
[super dealloc];
}