Refresh TableView data after button click
I am making a chat app and reading messages from a SQLite database. I want to reload the TableView data with new messages after clicking on the submit button. Event for my button:
[_sendButton addTarget:self action:@selector(saveMessage:) forControlEvents:UIControlEventTouchUpInside];
I think I need to use -(void)viewDidAppear:(BOOL)animated
, but it didn't work (or I'm doing something wrong).
[_tableView reloadData];
-(void)viewDidAppear:(BOOL)animated {
sqlite3 *database;
databaseName = @"Messenges.db";
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [documentPaths objectAtIndex:0];
databasePath = [documentsDir stringByAppendingPathComponent:databaseName];
messenges = [[NSMutableArray alloc] init];
if(sqlite3_open([databasePath UTF8String], &database) == SQLITE_OK) {
const char *sqlStatement = "select * from messenges";
sqlite3_stmt *compiledStatement;
if(sqlite3_prepare_v2(database, sqlStatement, -1, &compiledStatement, NULL) == SQLITE_OK) {
while(sqlite3_step(compiledStatement) == SQLITE_ROW) {
NSString *dbMessageText = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 1)];
Message *messege = [[Message alloc] initWithName:dbMessageText];
[messenges addObject:messege];
[messege release];
}
}
sqlite3_finalize(compiledStatement);
}
sqlite3_close(database);}
edit This is my method of adding a new row to the TableView. The new line should be added immediately after clicking the "Submit" button.
-(IBAction)insertRows:(id)sender {
MDAppDelegate *appDelegate = (MDAppDelegate *)[[UIApplication sharedApplication] delegate];
[messenges insertObject:[[NSString alloc] initWithFormat:@"%@", _textField.text] atIndex:appDelegate.messenges.count+1];
NSArray *insertIndexPaths = [NSArray arrayWithObject: [NSIndexPath indexPathForRow:1 inSection:1]];
UITableView *tV = (UITableView *)self.tableView;
[tV beginUpdates];
[tV insertRowsAtIndexPaths:insertIndexPaths withRowAnimation:UITableViewRowAnimationRight];
[tV endUpdates];}
But this code is giving me an error: 'Invalid table view update. The application has requested an update to the table view that is inconsistent with the state provided by the data source.'
How can I fix this?
Edit2
Ok. I have only 1 section. numberOfSectionsInTableView:
does not require correction.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
MDAppDelegate *appDelegate = (MDAppDelegate *)[[UIApplication sharedApplication] delegate];
NSUInteger numberOfRowsInSection = appDelegate.messenges.count; //add new variable to what rows will be +1
if (self.editing) {
numberOfRowsInSection++;
}
return numberOfRowsInSection;}
But I still have the same error.
Edit # 3
We'll see. I changed mine insertRows
to:
-(IBAction)insertRows:(id)sender {
MDAppDelegate *appDelegate = (MDAppDelegate *)[[UIApplication sharedApplication] delegate];
[self.tableView beginUpdates];
self.tableView.editing = YES;
[messenges insertObject:[[NSString alloc] initWithFormat:@"%@", _textField.text] atIndex:appDelegate.messenges.count+1];
NSArray *insertIndexPaths = [NSArray arrayWithObject: [NSIndexPath indexPathForRow:appDelegate.messenges.count+1 inSection:1]];
[self.tableView insertRowsAtIndexPaths:insertIndexPaths withRowAnimation:UITableViewRowAnimationRight];
[self.tableView endUpdates];
}
And numberOfRowsInSection
:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
MDAppDelegate *appDelegate = (MDAppDelegate *)[[UIApplication sharedApplication] delegate];
NSUInteger numberOfRowsInSection = appDelegate.messenges.count;
if (self.tableView.editing) {
numberOfRowsInSection++;
}
NSLog(@"%i",numberOfRowsInSection);
return numberOfRowsInSection;
}
But I got an error *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 4 beyond bounds [0 .. 3]'
. Then I adjusted if (self.tableView.editing)
to:
if (self.tableView.editing) {
MDAppDelegate *someNewDelegate = (MDAppDelegate *)[[UIApplication sharedApplication] delegate];
numberOfRowsInSection = someNewDelegate.messenges.count+1; //or .count
}
But the same error appeared regarding "NSRangeException".
source to share
To update UITableView
with animation you need:
- Call
beginUpdates
toUITableView
- Updating the data model
- Insert / delete lines and sections
- Call
endUpdates
toUITableView
A couple of things to remember:
- If you delete a section, you should also not delete lines from that section (unnecessary and may even cause errors).
- If you are inserting a section, you also must not insert rows in that section (
UITableView
will callnumberOfRowsInSection:
, thencellForRowAtIndexPath:
if the cell is visible).
What do you mean update my data model?
If you are in control of UITableView
adding and removing rows and sections, you should have some way of keeping track of which sections / rows are UITableView
at any given moment.
For example, you should have a method numberOfRowsInSection:
that resembles this:
- (NSInteger)numberOfRowsInSection:(NSInteger)section
{
// You should have a way to get section data with an index (section)
// The below example assumes an NSArray named self.mySectionCollection
// exists for this purpose
NSArray *sectionList = self.mySectionCollection;
// You should have data associated with a section. Below its assumed
// this data is wrapped in an NSDictionary. Data that might be in here
// could include a height, a name, number of rows, etc...
NSDictionary *sectionData = [sectionList objectAtIndex:section];
NSInteger rowCount = 0;
// SectionData should NEVER be nil at this point. You could raise an
// exception here, have a debug assert, or just return 0.
if (sectionData)
{
rowCount = [[sectionData objectForKey:@"rowCount"] intValue];
}
return rowCount;
}
You can store section / line information in several ways, including using CoreData. The fact is that by changing this collection between beginUpdates
and endUpdates
you are telling UITableView
how to update itself (it will ask numberOfRowsInSection:
, numberOfSections
and cellForRowAtIndexPath:
as needed).
Edit
I believe that if you change your method insertRows:
to change the datasource ( messenges
), in the meantime, you communicate UITableView
that updates have occurred, that everything will start working for you as expected.
Try using this code:
-(IBAction)insertRows:(id)sender
{
MDAppDelegate *appDelegate = (MDAppDelegate *)[[UIApplication sharedApplication] delegate];
UITableView *tV = (UITableView *)self.tableView;
[tV beginUpdates];
// UPDATE DATA MODEL IN BETWEEN beginUpdates and endUpdates
int rowIndex = appDelegate.messenges.count + 1;
[messenges insertObject:[[NSString alloc] initWithFormat:@"%@", _textField.text]
atIndex:rowIndex];
// Notify UITableView that updates have occurred
NSArray *insertIndexPaths = [NSArray arrayWithObject:
[NSIndexPath indexPathForRow:rowIndex inSection:1]];
[tV insertRowsAtIndexPaths:insertIndexPaths
withRowAnimation:UITableViewRowAnimationRight];
[tV endUpdates];
}
Edit # 2
If you still have problems I would take a look where you set the flag self.editing
.
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section
{
MDAppDelegate *appDelegate = (MDAppDelegate *)[[UIApplication sharedApplication] delegate];
//add new variable to what rows will be +1
NSUInteger numberOfRowsInSection = appDelegate.messenges.count;
if (self.editing)
{
numberOfRowsInSection++;
}
return numberOfRowsInSection;
}
This flag determines whether an additional table exists in the table or not. For this reason, you must set it between beginUpdates
and endUpdates
like this:
// assuming the editing flag is set from some IBAction
-addNewRow:(id)sender
{
int row = ???; // not sure where you want this new row
[self.tableView beginUpdates]
self.editing = YES;
NSArray *insertIndexPaths = [NSArray arrayWithObject:
[NSIndexPath indexPathForRow:row inSection:1]];
[self.tableView insertRowsAtIndexPaths:insertIndexPaths
withRowAnimation:UITableViewRowAnimationRight];
[self.tableView endUpdates];
}
Remember to also call deleteRowsAtIndexPaths:withRowAnimation:
if the user stops editing if you remove the added row. I believe no action is required if the edit becomes permanent, but you need to install self.editing = NO
as well as add a new line at the correct location in self.messenges
.
Also , in your method, insertRows:
you say UITableView
that you are inserting a row at index 1, when in fact you are always inserting at the end of the collection messenges
. I changed my version of the method insertRows
to have a variable rowIndex
in order to ensure that the same index is used when updating the data model and informing about the change UITableView
.
Finally, please include as much debugging information as possible when you run into problems. Usually when there is a problem with an update UITableView
in the way you are trying it will tell you that there is an inconsistency error. It has been a while since I saw it, but something that looks like there were 5 rows before the table was updated and only 1 was added after 7. I'd like to see this message if it popped up in your console.
Edit # 3
This is the answer to the error you are seeing:
* Application terminated due to uncaught "NSRangeException", reason: "* - [__ NSArrayM objectAtIndex:]: index 4 out of bounds [0 .. 3] '
Your error has nothing to do with nesting in a UITableView. This is because you are trying to insert outside of the array. I guess this is a violation:
[messenges insertObject:[[NSString alloc] initWithFormat:@"%@",
_textField.text]
atIndex:appDelegate.messenges.count+1];
To insert at the end of an array, use index appDelegate.messenges.count
(deleted +1
).
Also ... are you absolutely sure your data model and calls to update the UITableView agree? Try calling NSLog(@"rowsBeforeUpdate: %i", [self numberOfRowsInSection:0]);
immediately after the call beginUpdates
and just before the call endUpdates
. Also, call NSLog(@"inserting index paths: %@", insertIndexPaths)
when informing the UITableView of the insert operation. I am assuming that it self.messenges
accurately reflects the rows that should be in the table, but adding +1
when self.tableView.editing
is true, you push numberOfRowsInSection:
above what you report in your calls to insertRowsAtIndexPaths:withRowAnimation:
.
Try the following:
-(IBAction)insertRows:(id)sender {
MDAppDelegate *appDelegate = (MDAppDelegate *)[[UIApplication sharedApplication] delegate];
[self.tableView beginUpdates];
NSLog(@"rows before update: %i", [self numberOfRowsInSection:0]);
self.tableView.editing = YES;
NSLog(@"rows with editing flag set: %i", [self numberOfRowsInSection:0]);
int rowIndex = appDelegate.messenges.count; // don't need +1 at end
int sectionIndex = 0; // should it be 0 or 1? I would guess 0
[messenges insertObject:[[NSString alloc] initWithFormat:@"%@", _textField.text]
atIndex:rowIndex];
NSArray *insertIndexPaths = [NSArray arrayWithObject:
[NSIndexPath indexPathForRow:rowIndex
inSection:sectionIndex]];
[self.tableView insertRowsAtIndexPaths:insertIndexPaths
withRowAnimation:UITableViewRowAnimationRight];
NSLog(@"inserting index paths: %@", insertIndexPaths);
[self.tableView endUpdates];
}
I changed the line index above to exclude +1
in it. I changed the section index to 0, which should be correct, unless this UITableView actually has multiple sections that were not mentioned. Make sure your magazines make sense. If you see what numberOfRowsInSection:
has increased by two over the course insertRows:
, you would also be better off seeing what insertRowsAtIndexPaths:
reflects the same . I am confused why you need an edit flag to add another line to your method numberOfRowsInSection:
. This part (where you add an extra line if installed self.editing
) doesn't seem to work correctly. Maybe just try to keep that part so some of it works, then add it back in once you have something that works fine. Perhaps changenumberOfRowsInSection:
as follows, until some things start working:
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section
{
MDAppDelegate *appDelegate = (MDAppDelegate *)[[UIApplication sharedApplication] delegate];
NSUInteger numberOfRowsInSection = appDelegate.messenges.count;
NSLog(@"numberOfRowsInSection %i: %u", section, numberOfRowsInSection);
return numberOfRowsInSection;
}
source to share