How to access values entered in a UITableViewCell? - iphone

I have a prototype table in my app witch I populate with a customTableViewCell class with a UITextField inside.
In my navigation bar I got a save button.
The question is, how to access this dynamic created cell's to get the UITextField content?
This is my code, you can see that I tried to use NSMutableArray
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"customTableCell";
customTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
[self.pfCells addObject:cell];
if(cell == nil)
{
cell = [[customTableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
}
// Configuration
cell.lblName.text = [self.pfFields objectAtIndex: [indexPath row]];
cell.txtType = [self.pfTypes objectAtIndex: [indexPath row]];
if ([[self.pfTypes objectAtIndex:[indexPath row]] isEqualToString: #"n"]) {
[cell.txtField setKeyboardType:UIKeyboardTypeNumberPad];
} else if ([[self.pfTypes objectAtIndex:[indexPath row]] isEqualToString: #"m"]) {
[cell.txtField setKeyboardType:UIKeyboardTypeEmailAddress];
}
return cell;
}

Here's another way to save content from a UITextField contained in a UITableViewCell:
Inside tableView:cellForRowAtIndexPath: set the delegate and a tag for txtField
Implement textFieldDidEndEditing: check for a UITextField tag value an save data in a private variable
Reload UITableView
The biggest advantage of this implementation if the fact that you doesn't need to iterate over whole tableview everytime you change a textfield value.

Quick answer:
#pragma mark - UITextFieldDelegate
- (void)textFieldDidEndEditing:(UITextField *)textField
{
// grab the row we are working on
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
// remove the old key/value pair if it exists and add the new one
[self.modelDictionary removeObjectForKey:indexPath];
[self.modelDictionary setObject:textField.text forKey:indexPath];
}
Be sure to add cell.txtField.delegate = self when configuring your cell. Then in your save button, you'd iterate through the dictionary and save the values -- or just save the dictionary itself.
Also, if you are targeting iOS6 or later, use dequeueReusableCellWithIdentifier:forIndexPath: as this method guarantees a cell is returned and resized properly, so you don't have to check for nil and manually init your cell.
Longer answer:
You generally never want to store your model in your view as you are doing. Aside from it breaking the MVC design patterns, it also causes issues with UITableViews. Specifically, a UITableViewCell will be recycled when it scrolls off the screen. So any values you have in those fields are lost. While you can get away with doing this if you only have visible rows that never scroll off the screen, I would encourage you to avoid this approach altogether.
Instead, you should store the values entered into the textboxes in your model object. The easiest way to do this is to use UITextFieldDelegate's textFieldDidEndEditing: to grab the values after the user enters them, then add these values to your model. You model could be something as simple as an NSDictionary using the indexPath as the key.

Related

How can I store my UITableViewCells in a NSMutableArray?

Basically I'm making a list view that you can add things to the top of. The best way I can think of doing this is to store the UITableViewCells themselves in a NSMutableArray — Because I can simply pull them from the array them with all their data inside the object, and this list view will never be over 10 cells long.
Also note that I'm using Storyboards, hence the initWithCoder use.
The following code is what I'm trying, and it doesn't work:
// This is where my NSMutableArray is initialized:
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder]) {
if (!_CellsArray) {
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"TestCell"];
_CellsArray = [NSMutableArray arrayWithObject:cell];
}
}
return self;
}
//UITableView Delegate & DataSource Methods
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"TestCell"];
[_CellsArray insertObject:cell atIndex:0];
return [_CellsArray objectAtIndex:indexPath.row];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 10;
}
I realize I may be approaching this in the wrong way, that's why I'm here though :)
Thank you.
edit: fixed a type in the code (TimerCell -> UITableViewCell)
Let's look at the order things get called in and what happens.
Your view controller is unarchived, so your initWithCoder: method is called. This method creates a mutable array and puts one instance of TimerCell into it. Said instance is not further configured (unless you've overridden initWithStyle:reuseIdentifier: to do some configuration).
Your data source method tableView:numberOfRowsInSection: is called, and it tells the table view there are ten rows.
Thus, your tableView:cellForRowAtIndexPath: is called ten times. Each time, it creates a new instance of UITableViewCell and inserts it into your mutable array. (After ten calls, your mutable array contains one TimerCell at index 10 and ten UITableViewCells at indices 0-9.) It does nothing to configure the cell's contents or appearance, then it returns the cell at the specified row index. On the first call, you're asked for row 0, so the cell you just created and inserted at index 0 is returned. On the second call, you're asked for row 1, so the cell at index 1 in your array is returned -- since you just inserted a new cell at index 0, the cell you created on the last call has shifted to index 1, and you return it again. This continues with each call: you return the same unconfigured UITableViewCell ten times.
It looks like you're trying to out-think UIKit. This is almost never a good thing. (It's been said that premature optimization is the root of all evil.)
UITableView already has a mechanism for cell reuse; it's best to just keep track of your own cell content and let that mechanism do its thing. I took so long to type this that other answers have been written describing how to do that. Look to them, or to Apple's documentation or any third-party UITableView tutorial.
Why don't you just store the cell information in an array. Then in the -cellForRowAtIndexPath: method, just extract the data needed to change each cell.
Here is a simple example:
//Lets say you have an init like this that inits some cell information
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder]) {
cellArray = [NSArray alloc] initWithObjects:#"firstCell",#"secondCell",#"thirdCell",nil];
}
return self;
}
//then for each cell, just extract the information using the indexPath and change the cell that way
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
cell.textLabel.text = [cellArray objectAtIndex:indexPath.row];
return cell;
}
Table views don't store things. Rather, they just ask for the data they want to display, and you typically get that data from elsewhere (like an NSArray, or an NSFetchedResultsController). Just store the things you want into some data container, and let the table display them for you.
// Probably your data model is actually a member of your class, but for purposes of demonstration...
static NSArray* _myArray = [[NSArray alloc] initWithObjects:#"Bob", #"Sally", #"Joe", nil];
- (NSInteger) tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section
{
return [_myArray count];
}
- (UITableViewCell*) tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
static NSString* CellIdentifier = #"TestCell";
// Make a cell.
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if( cell == nil ) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
// Setup the cell with the right content.
NSString* aString = [_myArray objectAtIndex:[indexPath row]];
cell.textLabel = aString;
return cell;
}
Now if you want more stuff in the list, add it to your array, and you're done.
Edit: On another note, initWithCoder: isn't generally the best place to do initialization for a view controller. Reason being, at the point that it's called, there's a good chance that stuff isn't loaded yet (IBOutlets, for example). I tend to prefer viewDidLoad (don't forget to cleanup in viewDidUnload in that case), or awakeFromNib.

How to work with UITextFields inside of UITableViewCells?

I have 10 UITableViewCells in my UITableView. Each cell has a UITextField. They load fine, but I need a way to set each cell's text to its associated string.
Is there a delegate of UITextField that I can use to do this, and how can I determine which textfield belongs to what NSString, etc?
Edit:
Here is a picture of my tableView. I want it to load text into each cell's textfield from the server then the user can edit it. Or if there is no data on the server, the textfield will be blank and the user can add data and it will sync back.
I have created an NSString for each cell, such as temperatureString, pulseString, etc.
Edit: In respect to the new info this is my new solution
So by the looks of it you are inserting UITextField into each cell, instead of setting the tag of each cell set the tag for each UITextField
First Define your tags
#define DESCRIPTIVE_TAG_VALUE_1 10
#define DESCRIPTIVE_TAG_VALUE_2 11
#define DESCRIPTIVE_TAG_VALUE_3 12
...
Use these in your UITextField Delegate to determine which UITextField belongs to which NSString that is if you are syncing with each update, if your not. Then obtain a reference to the UITableView and retrieve each of the text values of each subview of a cell that has a tag equal to one of your defines (again in a switch statement).
Once again in when working with iOS use tags they are your friends
OR
Also you said that you are holding a reference to each NSString, you could just hold a reference to each UITextField instead that way when you sync you just have to retieve from each of your UITextField references.
But the Apple's best practises say to exercise the use of unique tags when dealing with mutliple views. It's really up to you
UITextfield is a subclass of UIView which has a 'tag' property. You can assign the cell's indexPath.row to be it's text field's tag as identification.
Based on your comment in response to #Javy (which contains extra information you should consider adding to your original question), you could do something like the following:
UITableViewCell *cell = nil;
NSString *key = nil;
switch (indexPath.row)
{
case 0:
cell = self.temperatureCell;
key = #"temperature";
break;
case 1:
// Do other cases similarly ...
}
NSString *text = [self.childAppointmentDictionary objectForKey:key];
cell.textField.text = text;
You should be setting the text in tableView: cellForRowAtIndexPath:
It sounds like you have a specific order that you want your items to appear in which would be a good time to use an NSArray, rather than an NSDictionary.
You could, in init, or initwithNibName, create a retained array property:
self.myListArray = [NSArray arrayWithObjects:#"fever", #"cough", #"runny nose", nil];
Then, assuming that you only have 1 section, you would do cell.textField.text = [self.myListArray objectAtIndex:indexPath.row];
Is there a reason that you're using an NSDictionary for this rather than an NSArray?
You must store a reference to each text field when they are created, so in the:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
Method, you would add this line:
[myTextFieldArray addObject: cell.textField];
Assuming textField is the property for your custom tabel view cell.
You would also set the text within the text field just after the if (cell == nil) method.
The delegate methods simply allow you to know when text is being typed, etc.
EDIT:
Considering what you've added, I agree with Sid and CStreel. Create the tags for each value:
#define kTemperatureTag 0
#define kPulseTag 1
// etc.
As an alternative to CStreel, I would assign/retrieve info matching the indexPath row, and not worry about assigning tags, because they will be the same thing if you start your tags at zero.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// setup / retrieve cell
NSInteger index = [indexPath row];
switch(index)
{
case kTemperatureTag:
cell.textField.text = [self.childAppointmentDictionary objectForKey:#"temperature"];
break;
// case ...
}
// ..
}
And:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSInteger index = [indexPath row];
switch(index){
case kTemperatureTag:
[self.childAppointmentDictionary addObject:cell.textField.text forKey:#"temperature"];

UITextField in UITableViewCell Help

I have scoured the internet looking for a good tutorial or posting about having a UITableView populated with a UITextField in each cell for data entry.
I want to keep track of each UITextField and the text written within it while scrolling. The tableView will be sectioned. I have been using a custom UITableViewCell but I'm open to any method.
Also, is it possible to use the textFields as ivars?
If any of you could point me in the right direction, it would be greatly appreciated.
Thank you in advance!
To solve your problem you have to maintain an array, with some number (number of textFields you added to all cells) of objects.
While creating that array you need add empty NSString objects to that array. And each time while loading the cell you have to replace the respected object to respected textField.
Check the following code.
- (void)viewDidLoad{
textFieldValuesArray = [[NSMutableArray alloc]init];
for(int i=0; i<numberofRows*numberofSections; i++){
[textFieldValuesArray addObject:#""];
}
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return numberofSections;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return numberofRows;
}
- (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:identifier];
CustomTextField *tf = [[CustomTextField alloc] initWithFrame:CGRectMake(5,5,290,34)];
tf.tag = 1;
[cell.contentView addSubView:tf];
[tf release];
}
CustomTextField *tf = (CustomTextField*)[cell viewWithTag:1];
tf.index = numberofSections*indexPath.section+indexPath.row;
tf.text = [textFieldValuesArray objectAtIndex:tf.index];
return cell;
}
- (void)textFieldDidEndEditing:(UITextField *)textField{
int index = textField.index;
[textFieldValuesArray replaceObjectAtIndex:index withObject:textField.text];
}
First of all, you must understand that UITableViewCell and UITextField are just views, they are not supposed to hold data, they are just supposed to display them and allow the user to interact with them: The data should remain stored in the controller of the table view.
You have to remember that UITableView allows you to reuse UITableViewCell instances for performance purpose: what's displayed on the screen are actually the only subviews UITableView keep there. It means that you'll reuse one cell that already has a text field in it and set the text on that field directly. When the user will tap on the field it will edit it and you'll have to get the value back from it when the user will have finished.
The fastest way, would be to use what Satya proposes, that is building normal UITableViewCell and insert into a UITextField (there's no need for a CustomTextField class...). The tag will allow you to get back to the text field easily... But you'll have to setup your text field so it behaves properly when the table view resizes or if a label in the same cell changes.
The cleanest way to do that is to subclass UITableViewCell and setup the layout of your label and text field, and you can provide the text field as a property of the custom subclass.
I have used Textfields in tableview for data entry.
I have customised the UITextField class in a separate class called Utility :
In Utility.h
#interface CustomUITextField:UITextField{
NSInteger rowNumber;
}
In Utility.m
#implementation CustomUITextField
#synthesize rowNumber;
#end
My tableView cellForRowAtIndexPath method is
- (UITableViewCell *)tableView:(UITableView *)theTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *Identifier = #"Cell";
UITableViewCell *cell = [theTableView dequeueReusableCellWithIdentifier:Identifier];
if(cell == nil)
cell = [self reuseTableViewCellWithIdentifier:Identifier withIndexPath:indexPath];
CustomUITextField *itemNameTextField = (CustomUITextField *)[cell.contentView viewWithTag:TEXTFIELD_TAG];//this is the tag I have set in reuseTableViewCellWithIdentifier method for textfield
itemNameTextField.rowNumber = indexPath.row;
itemNameTextField.text = #"";//you can set it for the value you want
if(itemListTable.editing)
itemNameTextField.borderStyle = UITextBorderStyleRoundedRect;
else
itemNameTextField.borderStyle = UITextBorderStyleNone;
return cell;
}
You can customise the delegate methods of UITextField for CustomUITextField & can save the text entered in a particular row's textfield by accessing the CustomTextField's row number.
Just try with this.
I had the same problem here is some code i found that treats this problem . it puts the data enterd in a Array Look at the Debugger console to see the results of the text being typed here's the link TextFieldCell. . Happy Codeing

How to populate a label field from a selected row in a uitableview

I have a uitableview that is populated from a sqlite query.
I want to select or click on a row and then display that row's value in a uilabel field. To show the user that the row was selected.
I also want to pass that value on to different controllers that will be called later.
Here is a copy of my cellForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"psystem";
PSystem *psystem = [self.ppdm_systems objectAtIndex:indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
// Set up the cell...
// self.accessoryType = UITableViewCellAccessoryCheckmark;
cell.textLabel.text = psystem.system_id;
return cell;
}
I took out the _label.text .... in my various experiments.
Now what is not working is the passing of the value to different controllers.
Using the example listed here, the source controller is TableViewController and is where the value is set. The target controller is DetailViewController.
I can pass the title of the tab bar in, but that's from TableView --> DetailView.
I am not sure how to pull from tableview; ie: Tableview <-- DetailView when I am in DetailView.
thx
In your UIViewController, implement:
- (MyObject *)valueForSelectedRow {
MyCell *cell = (MyCell *)[self.tableView cellForRowAtIndexPath:[self.tableView indexPathForSelectedRow]];
return cell.myObject;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Get value
MyObject *object = [self valueForSelectedRow];
// Update the label, assumed that _label is a pointer to a UILabel view object.
_label.text = object.myValue;
}
When you want to push a new view controller, you just call -valueForSelectedRow and then use that value to push the controller.
This is assumed that you have a UITableViewCell subclass, with a property set to some model object. When you don't have that and just set the text property, that NSString object will be your 'model' object, although it would be easier when your cells handle custom model objects.
EDIT: Thanks for editing your answer. I now have the information I need. In this line: cell.textLabel.text = psystem.system_id, you setup the cell by simply setting the textLabel's text property. This is what I described in the paragraph above. I always create a UITableViewCell subclass, with a property set the the complete PSystem object. When you assign a PSystem object to the cell, it will handle it's contents, so you can easily manage your view in the, well, view. That's a very compelled approach since you never have to look at the controller again to alter the view's contents.
However, it can be done the way you currently have it. It would look something like:
- (NSString *)valueForSelectedRow {
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:[self.tableView indexPathForSelectedRow]];
return cell.textLabel.text;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Get value
NSString *value = [self valueForSelectedRow];
// Update the label, assumed that _label is a pointer to a UILabel view object.
_label.text = value;
}
In this case, your PSystem model has been replaced with an NSString object. For this, it's enough, but it could be so much easier to have the object itself. Okay, that can also be done by selecting the PSystem object again from the p_system array by the NSIndexPath, but things will become harder once you come up with more complex tableviews.

UITableView won't update properly (even with call to reloadData)

I am producing an iPhone app for which part of the interface is exactly like the 'Most Popular' section of the iPhone YouTube app.
This 'popular' section is accessed from a Tab Bar at the bottom and the navigation bar at the top contains a UISegmentedControl to select 'Today, This Week, Month etc..'
Because most of the app consists of UITableViews with cells containing very similarly structured content, I have created a common MyAppTableViewController which inherits UITableViewController. My 'popular' section thus consists of a PopularTableViewController which inherits MyAppTableViewController. The actual UITableView resides within MyAppTableViewController.
PopularTableViewController has the method:
- (void) segmentChangeTimeframe:(id)sender {
UISegmentedControl *segCtl = sender;
if( [segCtl selectedSegmentIndex] == 0 )
{
// Call [self parse-xml-method-which-resides-in-MyAppTableViewController]
}
//... ... ...
}
The MyAppTableViewController makes use of NSXMLParser and thus has the code:
- (void)parserDidEndDocument:(NSXMLParser *)parser {
[self.myTableView reloadData];
}
(There are other methods which updates the data structure from which the table view gets it's data)
I have put console output code into the xml parsing methods, and when run, selecting the different segments causes the correct xml files to be parsed fine and the data structure seems to contain the correct values.
The problem is that the contents of the table cells wont change! grr! UNLESS!... A cell is scrolled out of view, and then back into view... THEN its changed!
I have done lots of searching about for this problem and one suggestion for a similar problem was to place the [self.myTableView reloadData] into its own method e.g. myReloadDataMethod and then use:
[self performSelectorOnMainThread:#selector(myReloadDataMethod) withObject:nil waitUntilDone:NO];
I tried placing the above code into the parserDidEndDocument method and it made absolutely no difference! I'm absolutely stumped and am wondering if anybody has any idea what's going on here.
Update:
The code to populate the cells is done with:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:MyIdentifier] autorelease];
}
// Set up the cell
int itemIndex = [indexPath indexAtPosition: [indexPath length] - 1];
NSString *artistName = [[myItemList objectAtIndex: itemIndex] objectForKey: #"itemA"];
NSString *mixName = [[myItemList objectAtIndex: itemIndex] objectForKey: #"itemB"];
cell.textLabel.text = itemA;
cell.detailTextLabel.text = itemB;
cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
return cell;
}
The above code is in MyAppTableViewController which is also where myItemList resides.
Your -performSelectorOnMainThread: code is for when you make changes to the model classes on a background thread. UI events (including -reloadData) need to occur on the main thread. If you're not using a background thread, then this is unnecessary. If you are, something like it is mandatory.
If you are changing the value of a specific cell, the way you achieve that is to change the cell itself. On iPhone, cells are full views (unlike on Mac), so if you want to change their data, you just change their data and call -setNeedsDisplay. You can get the cell (view) for a given location using -cellForRowAtIndexPath:. You can determine if a given cell is onscreen by using -indexPathsForVisibleRows or -visibleCells.
It is very rare to need to call -reloadData. You should only do that if you are throwing away everything and loading completely different data. Instead, you should use the insertion/deletion routines to add/remove rows, and you should just update the views of existing rows when their data change.
I had this same problem, and it was because I had a [tableView beginUpdates] call without an endUpdates call after.
Have you tried [tableView setNeedsDisplay:YES]?
After calling -reloadData, do you recieve callback to tableView:numberOfRowsInSection: ?
I'm almost sure, that self.myTableView is nil here:
- (void)parserDidEndDocument:(NSXMLParser *)parser {
[self.myTableView reloadData];
}