I went through all the examples online and could not figure out how to properly add a cell to a tableview with animation. Let's say I have one section with one cell and I want to add another cell once the user clicks on the first cell's accessory.
My "add" method does this:
- (IBAction) toggleEnabledTextForSwitch1onSomeLabel: (id) sender {
if (switch1.on) {
NSArray *appleComputers = [NSArray arrayWithObjects:#"WWWWW" ,#"XXXX", #"YYYY", #"ZZZZ", nil];
NSDictionary *appleComputersDict = [NSDictionary dictionaryWithObject:appleComputers forKey:#"Computers"];
[listOfItems replaceObjectAtIndex:0 withObject:appleComputersDict];
[tblSimpleTable reloadData];
}
Which is working but there is no animation. I understand that in order to add animation, I need to use insertRowsAtIndexPaths:withRowAnimation, so I tried tons of options but it always crashes when executing the insertRowsAtIndexPaths:withRowAnimation method.
My recent try was by doing this:
- (IBAction) toggleEnabledTextForSwitch1onSomeLabel: (id) sender {
if (switch1.on) {
NSIndexPath *path1 = [NSIndexPath indexPathForRow:1 inSection:0]; //ALSO TRIED WITH indexPathRow:0
NSArray *indexArray = [NSArray arrayWithObjects:path1,nil];
[tblSimpleTable insertRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationRight];
}
}
What am I doing wrong? How can I make this happen easily? I dont understand this whole indexPathForRow thing...I also dont understand how with this method I can add a label name to the new cell. Please help...thanks!!
It's a two step process:
First update your data source so numberOfRowsInSection and cellForRowAtIndexPath will return the correct values for your post-insert data. You must do this before you insert or delete rows or you will see the "invalid number of rows" error that you're getting.
Then insert your row:
[tblSimpleTable beginUpdates];
[tblSimpleTable insertRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationRight];
[tblSimpleTable endUpdates];
Simply inserting or deleting a row doesn't change your data source; you have to do that yourself.
The important thing to keep in mind when using insertRowsAtIndexPaths is that your UITableViewDataSource needs to match what the insert is telling it to do. If you add a row to the tableview, make sure the backing data is already updated to match.
First of all, you should update your data model just before update table itself.
Also you can use:
[tableView beginUpdates];
// do all row insertion/delete here
[tableView endUpdates];
And table will produce all changed at once with animation (if you specify it)
The insertRowsAtIndexPaths:withRowAnimation: AND the changes to your data model both need to occur in-between beginUpdates and endUpates
I've created a simple example that should work on its own. I spent a week fiddling around trying to figure this out since I couldn't find any simple examples, so I hope this saves someone time and headache!
#interface MyTableViewController ()
#property (nonatomic, strong) NSMutableArray *expandableArray;
#property (nonatomic, strong) NSMutableArray *indexPaths;
#property (nonatomic, strong) UITableView *myTableView;
#end
#implementation MyTableViewController
- (void)viewDidLoad
{
[self setupArray];
}
- (void)setupArray
{
self.expandableArray = #[#"One", #"Two", #"Three", #"Four", #"Five"].mutableCopy;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.expandableArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//here you should create a cell that displays information from self.expandableArray, and return it
}
//call this method if your button/cell/whatever is tapped
- (void)didTapTriggerToChangeTableView
{
if (/*some condition occurs that makes you want to expand the tableView*/) {
[self expandArray]
}else if (/*some other condition occurs that makes you want to retract the tableView*/){
[self retractArray]
}
}
//this example adds 1 item
- (void)expandArray
{
//create an array of indexPaths
self.indexPaths = [[NSMutableArray alloc] init];
for (int i = theFirstIndexWhereYouWantToInsertYourAdditionalCells; i < theTotalNumberOfAdditionalCellsToInsert + theFirstIndexWhereYouWantToInsertYourAdditionalCells; i++) {
[self.indexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
}
//modify your array AND call insertRowsAtIndexPaths:withRowAnimation: INBETWEEN beginUpdates and endUpdates
[self.myTableView beginUpdates];
//HERE IS WHERE YOU NEED TO ALTER self.expandableArray to have the additional/new data values, eg:
[self.expandableArray addObject:#"Six"];
[self.myTableView insertRowsAtIndexPaths:self.indexPaths withRowAnimation:(UITableViewRowAnimationFade)]; //or a rowAnimation of your choice
[self.myTableView endUpdates];
}
//this example removes all but the first 3 items
- (void)retractArray
{
NSRange range;
range.location = 3;
range.length = self.expandableArray.count - 3;
//modify your array AND call insertRowsAtIndexPaths:withRowAnimation: INBETWEEN beginUpdates and endUpdates
[self.myTableView beginUpdates];
[self.expandableArray removeObjectsInRange:range];
[self.myTableView deleteRowsAtIndexPaths:self.indexPaths withRowAnimation:UITableViewRowAnimationFade]; //or a rowAnimation of your choice
[self.myTableView endUpdates];
}
#end
For the swift users
// have inserted new item into data source
// update
self.tableView.beginUpdates()
var ip = NSIndexPath(forRow:find(self.yourDataSource, theNewObject)!, inSection: 0)
self.tableView.insertRowsAtIndexPaths([ip], withRowAnimation: UITableViewRowAnimation.Fade)
self.tableView.endUpdates()
Related
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!
I have a multi-section tableview. In edit mode I allow rows to be moved from one section to another. Once the final row is removed from one section I delete that section. So I am using deleteSection inside moveRowAtIndexPath.
When the final item is moved from the section, the section header disappears as planned. But there is a very strange animation bug, where the moved row seems to 'merge' with the row it is dropped above, and an empty row is displayed at the bottom of the 'to' section (probably because the numberOfRows for that section is correct, but 2 rows are in the same position). Even stranger, when I click the reorder control for this row (not moving the item, simply touching and releasing), the two items 'unmerge'.
I have posted a video demonstrating this.
I have tried wrapping my data changes and view changes in begin/end updates, but to no avail.
I have uploaded a test project here, and I will also post the code below. A couple of points:
I have tried to replicate my data source's format in the demo project, in case this is where the problem originates. The key thing is that my source is a composite array of two other arrays (though I can't see why this would be an issue).
To see the behavior in question, move the two rows in the bottom section, up into the top section. Don't drop them in the last row on the top section though, since this seems to work ok.
Moving rows the other way, from the top section to the bottom section, is buggy in this demo project.
Code (all of this is in the demo project):
I set up my arrays in loadView:
- (void)loadView{
array1 = [NSMutableArray array];
[array1 addObject:#"test 0"];
[array1 addObject:#"test 1"];
[array1 addObject:#"test 2"];
array2 = [NSMutableArray array];
[array2 addObject:#"test a"];
[array2 addObject:#"test b"];
[super loadView];
}
I also have a method that returns a combination of these arrays - this is used as the data source:
- (NSMutableArray *)sourceArray{
NSMutableArray *result = [NSMutableArray array];
if (array1.count > 0) {
[result addObject:array1];
}
if (array2.count >0) {
[result addObject:array2];
}
return result;
}
Which allows for very simple number of rows/sections:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return self.sourceArray.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [[self.sourceArray objectAtIndex:section] count];
}
Standard Cell/Header formatting:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell.textLabel.text = [[self.sourceArray objectAtIndex:indexPath.section] objectAtIndex:indexPath.row];
return cell;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return [NSString stringWithFormat:#"Section %i", section];
}
This is where I do the magic
// Override to support rearranging the table view.
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
NSMutableArray *fromArray = [self.sourceArray objectAtIndex:fromIndexPath.section];
NSMutableArray *toArray = [self.sourceArray objectAtIndex:toIndexPath.section];
NSString *movedObject = [[self.sourceArray objectAtIndex:fromIndexPath.section] objectAtIndex:fromIndexPath.row];
[fromArray removeObject:movedObject];
[toArray insertObject:movedObject atIndex:toIndexPath.row];
if ([self.tableView numberOfRowsInSection: fromIndexPath.section] == 0) {
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:fromIndexPath.section] withRowAnimation:UITableViewRowAnimationFade];
}
}
I notice that the row that comes from the to-be-deleted section is the one that disappears until you retouch the order control.
I suspect that when this datasource method is called by the tableview, its state is still in the middle of performing the move, so calling 'deleteSections' will make the table try and delete the row you're moving. It's not so much of a merge as the fact that it's fading away at the same rate as the section header, and the one below it is just scooting back up to fill the space.
Tapping the control causes the table view to rejigger itself and realize that the row isn't actually gone.
to try and work around this, try running the deletion in the next runloop, via a dispatch call, like:
if ([self.tableView numberOfRowsInSection: fromIndexPath.section] == 0) {
dispatch_async(dispatch_get_main_queue(), ^() {
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:fromIndexPath.section] withRowAnimation:UITableViewRowAnimationFade];
});
}
this will cause the deletion to run on the main thread still, but allow the 'moveRow' and whatever call stack it happens to be in finish up its logic before the deletion call
Your problem is in the animation. One is being done while another is not yet finished (moving & deleting animation) causing one cell to be drawn upon the other. You can verify this by moving the cells around again. The correct order will then be displayed. According to Apple's docs on the UITableView:
Note: The data source should not call setEditing:animated: from within its implementation of tableView:commitEditingStyle:forRowAtIndexPath:. If for some reason it must, it should invoke it after a delay by using the performSelector:withObject:afterDelay: method.
Therefore to fix this, do this to your code:
if ([self.tableView numberOfRowsInSection: fromIndexPath.section] == 0) {
[self performSelector:#selector(someMethod:) withObject:fromIndexPath afterDelay:1.0];
}
- (void) someMethod:(NSIndexPath *) fromIndexPath {
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:fromIndexPath.section] withRowAnimation:UITableViewRowAnimationFade];
}
Should work fine. Just change the delay to something shorter that suites you.
On the off chance that your rows or what's inside them can take focus, have you checked that you have called resignFirstResponder or [view endEditing:YES]? We saw this when we used text fields and (IIRC it was iOS 4 version dependent too) left the focus in one of the fields.
You have to reload the tableview after deleting the section. Try this code.
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
NSMutableArray *fromArray = [self.sourceArray objectAtIndex:fromIndexPath.section];
NSMutableArray *toArray = [self.sourceArray objectAtIndex:toIndexPath.section];
NSString *movedObject = [[self.sourceArray objectAtIndex:fromIndexPath.section] objectAtIndex:fromIndexPath.row];
[fromArray removeObject:movedObject];
[toArray insertObject:movedObject atIndex:toIndexPath.row];
if ([self.tableView numberOfRowsInSection: fromIndexPath.section] == 0) {
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:fromIndexPath.section] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView reloadData];
}
}
Swap the order of fromArray and toArray in your code. If the item has a retain count of 1 prior to removing it from the array, it will have a retain count of 0 before adding it to toArray.
If you swap the order, the item will go from retain count of 1 to 2 then back to 1 when the remove is complete.
I think the UITableViewRowAnimationFade animation is interfering with the UITableViewCell move animation. One thing you can try is to delay the section deletion a little bit late in order for the cell move row animation to finish.
Try replace your code with the following code.
-(void)deleteSection:(NSIndexSet*)indexSet
{
[self.tableView deleteSections:indexSet withRowAnimation:UITableViewRowAnimationFade];
}
// Override to support rearranging the table view.
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
NSMutableArray *fromArray = [self.sourceArray objectAtIndex:fromIndexPath.section];
NSMutableArray *toArray = [self.sourceArray objectAtIndex:toIndexPath.section];
NSString *movedObject = [[self.sourceArray objectAtIndex:fromIndexPath.section] objectAtIndex:fromIndexPath.row];
[fromArray removeObject:movedObject];
[toArray insertObject:movedObject atIndex:toIndexPath.row];
if ([self.tableView numberOfRowsInSection: fromIndexPath.section] == 0) {
[self performSelector:#selector(deleteSection:) withObject:[NSIndexSet indexSetWithIndex:fromIndexPath.section] afterDelay:1.0];
// [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:fromIndexPath.section] withRowAnimation:UITableViewRowAnimationFade];
}
}
a solution that lost animation on last row :
if([listOfItemsOnTransaction count]==indexPath.row){
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath, nil]
withRowAnimation:UITableViewRowAnimationFade];
[self.tableView reloadData];
}else
{
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath, nil]
withRowAnimation:UITableViewRowAnimationFade];
}
I currently have a UITableView that is populated by a .plist full of exercises. What I want to be able to do is access individual exercises within the table by storing each exercise that is clicked on into an array, that will later be used to populate a seperate UITableView.
How exactly do I get access to these individual cells so that I can store them into this array. Here is what I have so far:
-(IBAction) saveWorkout {
NSMutableArray *workout = [NSMutableArray arrayWithCapacity:10];
[workout addObject: ] // I'm assuming this is where I add a cell to an array (or atleast the cell's string).
}
Any help?
Without delving too much into the actual code part of your question, calling -cellForRowAtIndexPath to retrieve a title is (can be) extremely expensive, especially if it is called multiple times. Use -didSelectRowAtIndexPath: to get the index of the title within your datasource array, then add that object to your list. Call -saveWorkout when you are finished/reach a certain limit.
A same might look like:
-(void)didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
//other code and such...
//get the index of the object in our original array and add the corresponding object to the new array.
[customWorkoutArray addObject:[workoutArray objectAtIndex:indexPath.row]];
}
To restate #CodaFi in code:
#property (strong, nonatomic) NSMutableArray *selectedElements;
#synthesize selectedElements=_selectedElements;
- (NSMutableArray *)selectedElements {
if (!_selectedElements) {
_selectedElements = [NSMutableArray array];
}
return _selectedElements;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
id element = [self.myModel objectAtIndex:indexPath.row];
// this is the key: this array will now be the basis for your subsequent table of selected items
[self.selectedElements addObject:element];
// do something to your model here that indicates it's been selected
// have your cellForRow... method interrogate this key and draw something special
[element setValue:[NSNumber numberWithInt:1] forKey:#"selected"];
// reload that row so it will look different
[tableView beginUpdates];
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
-(void)didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
You can get the index here by indexPath.row
}
I added a button on the uinavigationbar I want to use it to clear all the rows of uitablview
How can I do that?
A bit late... but try this:
Where you want to clear the table:
// clear table
clearTable = YES;
[table reloadData];
clearTable = NO;
This function should look like:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if(clearTable)
return 0;
(...)
}
There should be a UITableView delegate method that populates the UITableView control:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
Find this method and change where the data for each cell is being pulled from. If you are currently using the indexPath to access an array of values to populate your tableView cells, you can programmatically switch to an array of empty values, or even just return empty cells from this method when the condition is right.
Simple.. set your data source (NSArray or NSDictionary) to nil and [self.tableView reloadData]!
What do you exactly mean by clearing the row? Do you still want them to be there, but without text? If yes, here's this code:
UITableViewCell *cell;
NSIndexPath *index;
for (int i = 0 ; i < count; i++) {
index = [NSIndexPath indexPathForRow:i inSection:0];
cell = [tableView cellForRowAtIndexPath:index];
cell.textLabel.text = #"";
}
If you want to delete them, you can use this code:
[uiTable deleteRowsAtIndexPaths:[NSArray arrayWithObject:index] withRowAnimation:UITableViewRowAnimationFade];
Before you are reloading the table remove all objects from your tableView array (The array which populated your tableView) like so:
[myArray removeAllObjects];
[tableView reloadData];
The crash is occurring because you need to reset number if rows count in the -
-(NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section
{
return [tempArray count];
}
one way is use a flag variable as below:
-(NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section
{
if (deleting)
return rowcount;
else
return [tempArray count];
}
Also you need to modify the deleting code as below:
deleting = YES;
for (i = [uiTable numberOfRowsInSection:0] - 1; i >=0 ; i--)
{
NSIndexPath *index = [NSIndexPath indexPathForRow:i inSection:0];
[uiTable deleteRowsAtIndexPaths:[NSArray arrayWithObject:index] withRowAnimation:UITableViewRowAnimationFade];
rowcount = i;
}
deleting = NO;
[uiTable reloadData];
in your .h file
BOOL deleting;
NSInteger rowcount;
Hope this helps...
The rowcount = i should go before the call.
[uiTable deleteRowsAtIndexPaths:[NSArray arrayWithObject:index]
withRowAnimation:UITableViewRowAnimationFade];
Otherwise, it will crash.
tableView.dataSource = nil
tableView.reloadData()
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];
}