iOS: dynamically adding data from UITableView to NSArray - iphone

I have a UITableView in which I have a custom prototype cell, defined in another class (CustomCell), with a UITextField in it. Every time I press a button, it calls a method called addItem, which creates a new cell. I want the texts in the UITextFields to go to an array. To try to explain it better, if I add 3 cells to the UITableView and input 3 texts in the corresponding UITextFields, I want the text in 1st cell to go to the array in index 0, the text in the 2nd to go to index 1 and the text in 3rd cell to go to index 2. My biggest problem is that I want to be able to go back to UITextField in cell 1 and update it, and have it dynamically update the NSArray object corresponding to it, that is, the one at index 0. I have no idea how to approach it. Can anybody help???
Thank you very much!!
my code (obs: itemTable is the UITableView):
MainViewController.m
#implementation addViewController{
NSInteger n;
NSString *aid;
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
-(void)viewWillAppear:(BOOL)animated{
n=1;
aid=#"";
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return n;
}
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier= #"Cell";
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[CustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell"];
}
cell.itemNumber.text=[NSString stringWithFormat:#"Item %d",indexPath.row];
return cell;
}
- (IBAction)addItem:(UIButton *)sender {
++n;
[_itemTable reloadData];
}
- (IBAction)removeItem:(UIButton *)sender {
if (n>=0)--n;
[_itemTable reloadData];
}
CustomCell.m:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {_itemValue = [[UITextField alloc]init];
_item = [[UILabel alloc]init];
[self.contentView addSubview:_itemValue];
[self.contentView addSubview:_item];
}
return self;
}
CustomCell.h
#interface CustomCell : UITableViewCell
#property (strong, nonatomic) IBOutlet UILabel *itemNumber;
#property (strong, nonatomic) IBOutlet UITextField *itemValue;
#end

First, when you create each text field, you make yourself that text field's delegate, so you will get messages whenever something happens in the text field.
Okay, so now when the user types in a text field, you will get messages, and you can modify your model (the array, which you should keep as an NSMutableArray I suppose). But to do that, you need to figure out which the heck cell contains the text field that this message is coming from! You will do that something like this:
- (void)textFieldDidEndEditing:(UITextField *)tf {
// some cell's text field has finished editing; which cell?
UIView* v = tf;
do {
v = v.superview;
} while (![v isKindOfClass: [UITableViewCell class]]);
CustomCell* cell = (CustomCell*)v;
// so which row is it?
NSIndexPath* ip = [self.tableView indexPathForCell:cell];
// aha! so now ip.row is the row, and you can update your data model
// ... left as an exercise for the reader ...
}
I do exactly this sort of thing in my book, in http://www.apeth.com/iOSBook/ch21.html#_editable_content_in_table_items (that's where the above code comes from), so take a look and see what ideas it gives you.

When the user is done entering text you could do something like the following which maps the index paths of the rows in your tableview to the indices in an array.
- (NSMutableArray *)updateText {
NSUInteger cellsCount = [self.tableView numberOfRowsInSection:0];
NSMutableArray *cellTextArray = [[NSMutableArray alloc] initWithCapacity:cellsCount];
for(NSInteger i = 0; i < cellsCount; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
CustomCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
NSString *item = cell.itemNumber.text;
[cellTextArray insertObject:item atIndex:i];
}
return cellTextArray;
}
Assuming your cell has the UITextFieldDelegate set, when the user is done entering text you can do something like this:
- (void)textFieldDidEndEditing:(UITextField *)textField {
[self.delegate didFinishEditing];
}
Where self.delegate is the UITableViewController, which in turn call updateText when necessary.
Things to be careful of - the for loop in updateText needs to loop over the tableview and dequeue cells for each index path. Simply using the tableview's visible cells would most likely leaving you missing text from cells that were off screen and got reused.
Hope this helps and good luck!

There are obviously a few aspects of this problem. First of all, you want to be able to recover references to the UILabel's, so that you can figure out which row a specific UILabel is in. I'd recommend doing this using the tag property, like this:
_item = [[UILabel alloc] init];
_item.tag = 100; // or any value
[self.contentView addSubview:_item];
You also need to set an action that gets called whenever the text in the label gets changed. You can do that like this:
[_item addTarget:someObject
action:#selector(labelChanged:)
forControlEvents:UIControlEventEditingChanged];
Whatever class someObject is, it needs to have a method with this signature:
- (void)labelChanged:(id)sender;
Inside that method you can check that sender is in fact a UILabel, and then you can access the new text with sender.text.
In order to figure out what point in the array to put the text in, you can declare a loop over the number of rows in your table:
for (int i = 0; i < [mainViewControllerInstance tableView:theTableInQuestion numberOfRowsInSection:0]; ++i) {
if(sender == [[mainViewControllerInstance tableView:theTableInQuestion
cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]] viewWithTag:100]) {
// Put `sender.text` in the appropriate spot in your array
}
}
A few additional notes: I'd use an NSMutableArray to keep track of your strings, since you'll be updating them, but I'm not entirely sure what best practices are here. You'll also want to make sure you initialize your array (whether you make it an NSArray or an NSMutableArray) to have the proper number of rows, and make sure that you add rows to it when the + button is pressed, or you'll risk getting an exception when you try to change an entry for a row that doesn't yet exist.

You might also want to have a look at the free Sensible TableView framework. The framework performs almost all what you need automatically. Should probably save you a lot of manual work. Good luck!

Related

iOS 6 Collection View Controller multiple dynamic cells

I am only a week into Xcode, trying to learn something, so I can contribute to a project..
So far I've been looking into tutorials and made something :) But I'm far from over. I hope someone will understand what I'll be asking..
I'm making an app that will take data from a server, the data will include thumbnail images, images and text. Right now I have a collection view controller, with a cell that has 3 buttons (I find them easier to place around in the collection view cell), the button's background is a specific image (hard coded for now), and it takes you to a view controller, where all the content for that item are shown, nothing special :)
But the layout (design) is hard for me to achieve. Basically (from my understanding) I need to have 4 collection view cells, with different positioned buttons. Now I have one collection view controller, with one collection view , containing one cell.
#import "CollectionViewController.h"
#import "CollectionViewCell.h"
#import "ImageDetailsViewController.h"
#import "StoryItem.h"
#interface CollectionViewController ()
{
NSArray *arrBigLeft;
NSArray *arrSmallTopR;
NSArray *arrSmallBotR;
}
#end
#implementation CollectionViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
arrBigLeft = [[NSArray alloc]initWithObjects:#"imageicon0#2x.jpg", #"imageicon1#2x.jpg", #"imageicon2#2x.jpg", #"imageicon3#2x.jpg", nil];
arrSmallTopR = [[NSArray alloc]initWithObjects:#"imageicon4#2x.jpg", #"imageicon5#2x.jpg", #"imageicon6#2x.jpg", #"imageicon7#2x.jpg", nil];
arrSmallBotR = [[NSArray alloc]initWithObjects:#"imageicon8#2x.jpg", #"imageicon9#2x.jpg", #"imageicon0#2x.jpg", #"imageicon1#2x.jpg", nil];
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([segue.identifier isEqualToString:#"photoDetail1"]){
CollectionViewCell *cell = (CollectionViewCell *)sender;
NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell];
ImageDetailsViewController *idvc = (ImageDetailsViewController *)[segue destinationViewController];
idvc.img = [UIImage imageNamed:[arrBigLeft objectAtIndex:indexPath.item]];
}
if([segue.identifier isEqualToString:#"photoDetail2"]){
CollectionViewCell *cell = (CollectionViewCell *)sender;
NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell];
ImageDetailsViewController *idvc = (ImageDetailsViewController *)[segue destinationViewController];
idvc.img = [UIImage imageNamed:[arrSmallTopR objectAtIndex:indexPath.item]];
}
if([segue.identifier isEqualToString:#"photoDetail3"]){
CollectionViewCell *cell = (CollectionViewCell *)sender;
NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell];
ImageDetailsViewController *idvc = (ImageDetailsViewController *)[segue destinationViewController];
idvc.img = [UIImage imageNamed:[arrSmallBotR objectAtIndex:indexPath.item]];
}
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return [arrBigLeft count];
}
// The cell that is returned must be retrieved from a call to -dequeueReusableCellWithReuseIdentifier:forIndexPath:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
static NSString *identifier=#"Cell";
CollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
[[cell button1]setBackgroundImage:[UIImage imageNamed:[arrBigLeft objectAtIndex:indexPath.item]] forState:UIControlStateNormal];
[[cell button2]setBackgroundImage:[UIImage imageNamed:[arrSmallTopR objectAtIndex:indexPath.item]] forState:UIControlStateNormal];
[[cell button3]setBackgroundImage:[UIImage imageNamed:[arrSmallBotR objectAtIndex:indexPath.item]] forState:UIControlStateNormal];
return cell;
}
-(IBAction)returned:(UIStoryboardSegue *)segue {
}
#end
This doesn't work as I want it to yet, but at least I'm getting dynamically created cells. I'd like to be able to return multiple cells, with different arrays of items (images), and they all should load at the same time
here you can see only two cells, but the second one I added just to visualize what I'm trying to explain (4 different cells)
The first cell (because of the number of images) repeats 4 times when I ran the app, if I was about to add the same images to the second cell, I would like to see them as in the screen shoot, and to continue to repeat in that order equal to the number of images (length of array)
If you need more code, let me know.. thanks!
I suggest following this to get a basic understanding of whats required.
http://www.raywenderlich.com/22324/beginning-uicollectionview-in-ios-6-part-12
Than you can advance to more complex designs. Looks like your jumping into the deep end. But this tutorial is really good.

iPhone didSelectRowAtIndexPath not invoke for cells number 10, 20,30

here is the thing. I have a tableview with a variable number of rows. When you tap one of them, a detail view is shown.
Everything is ok if you tapped rows from 0 to 9, 11 to 19, 21 to 29 etc... But it doesn't work for rows number 10, 20, 30 etc... Even a long tap is detected on this rows, so an alert is shown, in order to delete the row if necessary (and I can remove it without problems), but didselectrowatindexpath is Never called.
I think is something involved that the tableview only keeps 10 rows at the same time. But in cellForRowAtIndex everything loads ok, for all rows. I keep all the data in some NSMutableArray, to access the data I need with [indexPath row].
I have already spend some time looking for similar post, but don't find anything like this.
any help will be appreciate. Thanks!
Some code:
// in .h
#import <UIKit/UIKit.h>
#import "PullRefreshTableViewController.h"
#import "DetalleMailViewController.h"
#interface MensajesTableViewController : PullRefreshTableViewController <DetalleMailViewControllerDelegate>
#property (nonatomic, strong) NSMutableArray *mailAuthors;
#property (nonatomic) int row_tabla_delete;
- (IBAction)presentMenu:(id)sender;
#end
//in .m make the synthesize of mailAuthors and some relevant code:
- (void)viewDidLoad
{
[super viewDidLoad];
self.tableView.allowsSelection = YES;
//This gesture works fine
UILongPressGestureRecognizer *lpgr = [[UILongPressGestureRecognizer alloc]
initWithTarget:self action:#selector(presentMenu:)];
lpgr.minimumPressDuration = 0.5; //seconds
lpgr.numberOfTouchesRequired =1;
lpgr.delegate = self;
[self.tableView addGestureRecognizer:lpgr];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *idCell = #"mailTableCell";
MailCell *celda = [tableView dequeueReusableCellWithIdentifier:idCell];
if(celda == nil){
celda = [[MailCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:idCell];
}
celda.mailAuthor.text = [self.mailAuthors objectAtIndex:[indexPath row]];
return celda;
}
-(void) viewWillAppear:(BOOL)animated{
[[self tableView] reloadData];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//I really use a segue here, but for the sake of simplicity, I just put a trace, never called for rows 10, 20, 30... The segue works fine the rest of the time...
NSLog(#"tapped %d", [indexPath row]);
}
- (IBAction)presentMenu:(id)sender {
if ([sender state] == UIGestureRecognizerStateBegan){
CGPoint p = [sender locationInView:self.tableView];
NSIndexPath * indexPath = [self.tableView indexPathForRowAtPoint:p];
if (indexPath != nil){
self.row_tabla_delete = [indexPath row];
}
//some code to delete the row
}
}
In MailCell.hI have theUILabelfor mailAuthor as a property, and linked in the storyboard with the UILabel inside theUITableViewCell. In the storyboard, I have aTableViewController linked to my own class "MensajeTableViewController", theUITableViewCellto my "MailCell`".
If there is some need to add some other relevant code, or something else, please Help me.

Unable to flush reusable UITableViewCell data

I have a UITableView with a custom UITableViewCell.
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//create the cell
MyCell *cell = (MyCell*)[tableView dequeueReusableCellWithIdentifier:#"MyIdentifier"];
cell.label.text = ..
cell.label2.text = ..
cell.label3.text = ..
Everything works fine, all my data loads properly, etc.
Now, I have a button on this View Controller that opens another view where the user can select which labels to display. So, for instance, display labels 1 and 3, but not 2...
Then, when Done is clicked, I want the tableView to be updated to reflect the new options, but since the cells were loaded with a reuseCellId, no changes are shown. How can I force the cells to recreate?
I thing that the best thing you could do is to store the cells configuration in some kind of structure (a set with the labels indices to be shown would be ok here) and alter this structure with your buttons and reload the table view. Then, on your tableView:cellForRowAtIndexPath: method, you should check that configuration structure in order to know what buttons should be visible.
This code may help:
#interface MyViewController : UIViewController
{
...
NSMutableSet *_labelsToShow;
}
...
#property (nonatomic, retain) NSMutableSet labelsToShow
#end
#implementation MyViewController
#synthesize labelsToShow = _labelsToShow;
- (void)dealloc
{
[_labelsToShow release];
...
}
//you may know which button has to add/remove each label, so this needs to be fixed with your logic
- (IBAction)myButtonAction:(id)sender
{
if (hasToShowLabel)
{
[self.labelsToShow addObject:[NSNumber numberWithInteger:labelIdentifier]];
} else
{
[self.labelsToShow removeObject:[NSNumber numberWithInteger:labelIdentifier]];
}
}
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"myCell";
MyCustomCell *cell = (MyCustomCell *)[tableView dequeReusableCellWithIdentifier:cellIdentifier];
if (cell == nil)
{
cell = [[[MyCustomCell alloc] initWithStyle:UITableViewCellStyleDefault] autorelease];
}
cell.label0.hidden = (![self.labelsToShow containsObject:[NSNumber numberWithInteger:0]]);
cell.label1.hidden = (![self.labelsToShow containsObject:[NSNumber numberWithInteger:1]]);
cell.label2.hidden = (![self.labelsToShow containsObject:[NSNumber numberWithInteger:2]]);
...
return cell;
}
#end
Good luck with this!
This is NOT a good approach
One way you can do this by using different identifier when you want to refresh cells
I am not sure if there is any other better way of doing this.
I solved this issue by just destroying the tableview, and recreating it every time.

Custom UITableViewCell redraw issues

I have a custom UITableView cell that I've added a textbox to for editing, that shows and hides based on the edit mode. I've also tried adding a vertical line that shows when editing, and it does that, but I'm running into some drawing issues. I just added a green checkmark rightView to start working on input validation feedback, and I'm seeing similar issues.
Here is the code for the cell, and part of my cellForRowAtIndexPath.
#import <UIKit/UIKit.h>
#interface EditableCellStyle2 : UITableViewCell {
CGRect editRect;
UITextField *editField;
UIView *lineView;
}
#property (nonatomic, readonly, retain) UITextField *editField;
#property (nonatomic, readonly, retain) UIView *lineView;
#end
#import "EditableCellStyle2.h"
#implementation EditableCellStyle2
#synthesize editField;
#synthesize lineView;
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code.
editRect = CGRectMake(83, 12, self.contentView.bounds.size.width-83, 19);
editField = [[UITextField alloc] initWithFrame:editRect];
editField.font = [UIFont boldSystemFontOfSize:15];
editField.textAlignment = UITextAlignmentLeft;
editField.textColor = [UIColor blackColor];
editField.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleHeight;
[self.contentView addSubview:editField];
self.editField.enabled = NO;
self.editField.hidden = YES;
lineView = [[UIView alloc] initWithFrame:CGRectMake(80, 0, 1, self.contentView.bounds.size.height)];
self.lineView.backgroundColor = [UIColor lightGrayColor];
[self.contentView addSubview:lineView];
self.lineView.hidden = YES;
}
return self;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state.
}
-(void)layoutSubviews
{
[super layoutSubviews]; // layouts the cell as UITableViewCellStyleValue2 would normally look like
editRect = CGRectMake(83, 12, self.contentView.frame.size.width-self.detailTextLabel.frame.origin.x-10, 19);
editField.frame = editRect;
}
- (void)willTransitionToState:(UITableViewCellStateMask)state {
[super willTransitionToState:state];
if (state & UITableViewCellStateEditingMask) {
self.detailTextLabel.hidden = YES;
self.editField.enabled = YES;
self.lineView.hidden = NO;
self.editField.hidden = NO;
}
}
- (void)didTransitionToState:(UITableViewCellStateMask)state {
[super didTransitionToState:state];
if (!(state & UITableViewCellStateEditingMask)) {
self.editField.enabled = NO;
self.editField.hidden = YES;
self.lineView.hidden = YES;
self.detailTextLabel.hidden = NO;
self.editField.text = self.detailTextLabel.text;
}
}
- (void)dealloc {
[editField release];
[lineView release];
[super dealloc];
}
#end
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// handling every section by hand since this view is essentially static. Sections 0, 1, 2, and 4 use a generic editable cell.
// Section 3 uses the multiline address cell.
static NSString *CellIdentifier = #"Cell";
EditableCellStyle2 *cell = (EditableCellStyle2 *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (indexPath.section == 0 || indexPath.section == 1 || indexPath.section == 2 || indexPath.section == 4) {
if (cell == nil) {
cell = [[[EditableCellStyle2 alloc] initWithStyle:UITableViewCellStyleValue2 reuseIdentifier:CellIdentifier] autorelease];
}
}
// Configure the Odometer
if (indexPath.section == 0) {
NSArray *array = [sectionsArray objectAtIndex:indexPath.section];
NSDictionary *dictionary = [array objectAtIndex:indexPath.row];
cell.textLabel.text = #"Odometer";
cell.detailTextLabel.text = [NSString stringWithFormat:#"%#", [dictionary objectForKey:#"Odometer"]];
cell.tag = kOdometer;
cell.editField.text = cell.detailTextLabel.text;
cell.editField.placeholder = #"Odometer";
cell.editField.tag = kOdometer;
cell.editField.keyboardType = UIKeyboardTypeNumberPad;
// Create a view for the green checkmark for odometer input validation and set it as the right view.
UIImage *checkImage = [UIImage imageNamed:#"tick.png"];
UIImageView *checkImageView = [[[UIImageView alloc] initWithImage:checkImage] autorelease];
cell.editField.rightView = checkImageView;
cell.editField.rightViewMode = UITextFieldViewModeAlways;
}
return cell;
}
There is more to it but all the cells are built the same way.
The problems are that, when in edit mode, the vertical lines will display properly. When I leave edit mode, any cells that were off screen when I go to normal mode still have the vertical line (it doesn't get hidden). Also, now that I've added the imageView for the checkmark indicator, any cells that are off screen when switching modes gain the checkmark. (only section 0 sets it up).
I've also noticed that if i do cell.setNeedsDisplay, the text label and detail text label won't update if the data source has been updated. I have to do [self.tableView reloadData] which skips any active animations.
I'm sure these issues are related to me using a custom cell + dequeueReusableCellWithIdentifier, but I can't find exactly what.
Any feedback or a push in the right direction would be appreciated.
Edit:
Not using reusable cells seems to have resolved the above issues. I'm still open to feedback on the cell code.
I forgot one other issue that may or may not be related. One of my cells has a "tap to view list" button. If I enter data into the cells while in edit mode, then hit that button to choose some info from a list (it displays a modal table view), when I dismiss the modal view, all of the cells' edited data has reverted to their original state. I'm not calling reload data when I dismiss the modal view controller. I thought this might be fixed by not using reusable cells but it isn't.
You need to prepare the cell for reuse. Try adding this to the EditableCellStyle2 implementation:
- (void)prepareForReuse {
[super prepareForReuse];
[self didTransitionToState:UITableViewCellStateDefaultMask];
}
Maybe you trimmed too much for your post, but in the posted code your reusable cell handling is all wrong.
First of all, each different type of cell needs its own CellIdentifier. In your case (judging from your code comment), that means at least a different identifier for section 3 versus sections 0, 1, 2, and 4. You may also want to do a separate identifier for section 0, so you don't have to keep removing and readding that checkmark. The different identifier needs to be used for both the dequeueReusableCellWithIdentifier: and initWithStyle:reuseIdentifier:` for the appropriate sections.
The second problem is that you are not resetting the cells correctly. There are two "kinds" of initialization that must be done to a UITableViewCell: initialization that is the same for every cell of its type, and initialization that depends on the specific row being displayed. The first kind can (and should) only be done once, when a new cell is allocated. The second kind must be done every time through tableView:cellForRowAtIndexPath:. You seem to be doing the first correctly for your EditableTableCell2 class in its init method, but I see nowhere in there where you do the per-row initialization: you never reset selected, or the cell state, or the contents of the edit field, or remove the checkImageView since you are using the same kind of cell for section 0 versus the other sections. If you want, the reset selected, state, and clearing out the checkbox image and field contents can be done in prepareForReuse on your EditableTableCell2 class.
The third problem, which is almost certainly due to over-trimming, is that you never create this "multiline address" cell for section 3. You'll end up maybe reusing a random EditableTableCell2, or maybe crashing on an exception from the framework when you return nil from tableView:cellForRowAtIndexPath:.

Dynamically Changing UITableView's Contents

I have an NSURL object which gets data from my site, based on a variable entered by the user into the search bar.
I split this data into an NSArray.
Once I have done that I wish to display the data in a UITableView.
My question is this. Is it possible to load the data into a UITableView dynamically?
i.e. Program loads, no data so UITableView is empty, then the user searches for one variable. Gets some data and the contents is loaded into the UITableView. Searches for a new variable, old data is cleared from UITableView and the new data is added?
I'm currently trying to do this using interface builder, but fear that I may have to make my interface pragmatically, so that I could destroy and re-create the UITableView, but i'm, not sure.
Thanks for any help.
Sure the method reloadData on UITableView will do the trick
Fear not, subclassing UITableView is very easy. In xCode simply choose new file, choose "Cocoa Touch Classes", "Objective-c class" and in the "Subclass of" dropdown pick "UITableView". xCode will add a UITableViewController subclass complete with stubs to build on.
I filled in a very simple example that draws the table data from an array and is displayed from the Application Delegate. As you suggested sending a reloadData message to the UITableView will refresh the displayed data.
As you probably found out, using InterfaceBuilder for this job is a lot harder than doing it programatically.
Cheers, niels
//
// MyTableViewController.m
// TableView
//
// Created by Niels Castle on 7/15/09.
// Copyright 2009 Castle Andersen ApS. All rights reserved.
//
#import "MyTableViewController.h"
#implementation MyTableViewController
// Initializer do custom initialisation here
- (id)initWithStyle:(UITableViewStyle)style {
if (self = [super initWithStyle:style]) {
// This is the source of my data. The simplest source possible,
// an NSMutableArray, of name. This would be the data from your web site
array = [[NSMutableArray alloc]
initWithObjects:#"Niels", #"Camilla", #"William", nil];
}
return self;
}
// How many section do we want in our table
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
// Customize the number of rows in the table view
// Simply the number of elements in our array of names
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return [array count];
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Reuse cells
static NSString *id = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:id];
if (cell == nil) {
cell = [[[UITableViewCell alloc]
initWithStyle: UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier] autorelease];
}
// Simplest possible cell - displaying a name from our name array
[[cell textLabel] setText: [array objectAtIndex:[indexPath row]]];
return cell;
}
- (void)dealloc {
[super dealloc];
[array release];
}
#end
//
// TableViewAppDelegate.m
// TableView
//
// Created by Niels Castle on 7/15/09.
// Copyright Castle Andersen ApS 2009. All rights reserved.
//
#import "TableViewAppDelegate.h"
#import "MyTableViewController.h"
#implementation TableViewAppDelegate
#synthesize window;
- (void)applicationDidFinishLaunching:(UIApplication *)application {
MyTableViewController *twc = [[MyTableViewController alloc]
initWithStyle: UITableViewStylePlain];
[window addSubview: [twc view]];
[window makeKeyAndVisible];
}
- (void)dealloc {
[window release];
[super dealloc];
}
#end
It's a bit complicated, but my solution that is working very reliably is following:
(assume that you have an array as bunch of arrays, that each represents a section and contains items that are in fact rows in table).
This example fits at the situation, when we load some data from server (e.g. JSON), and the result can be very different in number of sections and/or rows.
void function, you can omit it
-(void)addToPropertiesTable {
//fullTableData is above mentioned two dimensional array
int sectionsCount = _fullTableData.count;
int count = 0;
NSMutableArray *insertIndexPaths = [NSMutableArray array];
NSMutableArray *deleteIndexPaths = [NSMutableArray array];
for(int j = 0; j < sectionsCount; j++) {
NSMutableArray *currentAdverts = [[NSMutableArray alloc] init];
[currentAdverts addObjectsFromArray:[_fullTableData objectAtIndex:j]];
count = [currentAdverts count];
int currentRowsInSection = [self.propertiesTable numberOfRowsInSection:j];
if(currentRowsInSection > 0) {
//if any data in current tableView, lets get rid of them first
for (int i = [self.propertiesTable numberOfRowsInSection:j] - 1; i >=0 ; i--)
{
[deleteIndexPaths addObject:[NSIndexPath indexPathForRow:i inSection:j]];
}
}
for (NSUInteger item = 0; item < count; item++) {
[insertIndexPaths addObject:[NSIndexPath indexPathForRow:item inSection:j]];
}
}
[self.propertiesTable beginUpdates];
//we delete old rows - whether we need them or not
[self.propertiesTable deleteRowsAtIndexPaths:deleteIndexPaths
withRowAnimation:UITableViewRowAnimationFade];
if([self.propertiesTable numberOfSections]) {
//if any sections, we remove them
NSIndexSet *nsIndexSetToDelete = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0,[self.propertiesTable numberOfSections])];
[self.propertiesTable deleteSections:nsIndexSetToDelete withRowAnimation:UITableViewRowAnimationAutomatic];
}
//here we have to set new sections, whether they have changed or not
NSIndexSet *nsIndexSetToInsert = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0,sectionsCount)];
[self.propertiesTable insertSections:nsIndexSetToInsert withRowAnimation:UITableViewRowAnimationAutomatic];
//finally we insert rows
[self.propertiesTable insertRowsAtIndexPaths:insertIndexPaths
withRowAnimation:UITableViewRowAnimationFade];
[self.propertiesTable endUpdates];
//now we see the change in UI
[self.propertiesTable reloadData];
}