3

I'm making chat application and read messages data from SQLite database. I want to reload my TableView data with new messages after clicking on my "send" 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);}

Editing This is my method to add a new row to TableView. New row must be added at once after clicking "send" 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 give me 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 it?

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

Let's look. I changed my 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 corrected if (self.tableView.editing) to:

if (self.tableView.editing) {
    MDAppDelegate *someNewDelegate = (MDAppDelegate *)[[UIApplication sharedApplication] delegate];
    numberOfRowsInSection = someNewDelegate.messenges.count+1; //or .count
}

But got same error about 'NSRangeException'.

RomanHouse
  • 2,552
  • 3
  • 23
  • 44
  • 1
    Check out [Animate the insertion of a new section in UITableVIew](http://stackoverflow.com/a/9485899/590956). It talks about animating insertion of rows in an existing section as well. – Sam Mar 22 '12 at 16:22
  • 2
    I just wanted to point out that, you spelled what I think is supposed to be _message_, three different ways: message, messenge, and messege. I think only one of those is right. If these are somehow supposed to be four different words, I think you need a different naming scheme. – elusive Mar 22 '12 at 17:00
  • @Sam I have one question about your link. These methods have the ability to adding new row in real time after clicking "send" button? As well as in iPhone's "Messanges"? – RomanHouse Mar 23 '12 at 20:35
  • 1
    @RomanHouse Yes, you can update the tableview rows in real time any time you want. The trick is to update whatever data model you are using to show the tableview as you call the `insertRowsAtIndexPaths:withRowAnimation:` method. Note that you can insert them one by one calling this even for bulk updates. Just also remember to call that wedged between `beginUpdates` and `endUpdates`. – Sam Mar 23 '12 at 20:39
  • @Sam Hmm.. I can't understand how can I add new row in real time. With Editing Mode it is not a problem. But what I should do in case without using Editing Mode and I have some value in text field what I need to add in my TableView? – RomanHouse Mar 25 '12 at 12:13
  • The error you are getting means that your reported updates (like insertRowsAtIndexPaths:withRowAnimation:) doesn't agree with your datasource methods (like numberOfRowsInSection:) after endUpdates is called. In other words, if you have 5 rows in section 1 and add 2 then you had better have 7 after. – Sam Mar 31 '12 at 14:18
  • @Sam added my new `numberOfRowsInSection` – RomanHouse Mar 31 '12 at 15:28
  • @RomanHouse Looks like you were updating your data model (`messenges`) before calling `beginUpdates` and `endUpdates`. You should be modifying your data source in between `beginUpdates` and `endUpdates`. It doesn't matter the order you call `insertRowsAtIndexPaths:withAnimation` and update your data model so long as they are in between `beginUpdates` and `endUpdates`. I modified my answer with code to replace your `insertRows:` method with one I believe will resolve your issue. – Sam Mar 31 '12 at 20:40

1 Answers1

7

To update your UITableView with animation you need to:

  1. Invoke beginUpdates on your UITableView
  2. Update your data model
  3. Insert / Remove rows and sections
  4. Invoke endUpdates on your UITableView

Couple of Things to Keep in Mind:

  • If you are deleting a section you should not also be deleting rows from that section (unnecessary and might even cause errors)
  • If you are inserting a section you should not also be inserting rows into that section (UITableView will call numberOfRowsInSection: followed by cellForRowAtIndexPath: if the cell is visible).

What Do You Mean, Update My Data Model?

If you are manipulating a UITableView by adding and removing rows and sections, then you should have some way of keeping track of what sections / rows are currently in the UITableView at any given moment.

For example, you should have a numberOfRowsInSection: method 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 information about sections / rows in several different ways including using CoreData. The point is that by modifying this collection in between beginUpdates and endUpdates you are telling the UITableView how to update itself (it will query numberOfRowsInSection:, numberOfSections, and cellForRowAtIndexPath: as needed).

Edit

I believe if you modify your insertRows: method to modify your data source (messenges) at the same time you notify UITableView that updates have occurred that things will begin working for you properly.

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 are still having issues, I would look at where you are setting the self.editing flag.

- (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 controls whether an additional row exists in the table or not. For this reason, you must set it between beginUpdates and endUpdates like:

// 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 similarly call deleteRowsAtIndexPaths:withRowAnimation: if the user stops editing if you are removing the row you add. I believe no action is necessary if the edit becomes permanent, but you'd need to set self.editing = NO while also adding a new row to it's proper place in self.messenges.

Also, in your insertRows: method, you telling the UITableView that you are inserting a row at index 1 when in fact you always insert at the end of the messenges collection. I've modified my version of the insertRows method so that it has a rowIndex variable for the purpose of ensuring the same index is used when updating the data model and informing UITableView of the change.

Lastly, please include as much debugging information as possible when you run into problems. Usually when a problem arises from updating the UITableView in the manner you are trying, it will tell you there is an inconsistency error. It's been awhile since I've seen it, but something to the effect that before updating the table there were 5 number of rows and after there were 7 when only 1 was added. I'd like to see this message if it is showing up in your console.

Edit #3

This is in response to the error you are seeing:

* Terminating app due to uncaught exception 'NSRangeException', reason: '* -[__NSArrayM objectAtIndex:]: index 4 beyond bounds [0 .. 3]'

Your error has nothing to do with inserting into the UITableView. It has to do with the fact that you are trying to insert beyond the bounds of an array. My guess is the offending line is:

[messenges insertObject:[[NSString alloc] initWithFormat:@"%@", 
                          _textField.text] 
                atIndex:appDelegate.messenges.count+1];

To insert at the end of the array, use an index of appDelegate.messenges.count (removed the +1).

Also... Are you absolutely certain your data model and calls to update the UITableView agree? Try invoking NSLog(@"rowsBeforeUpdate: %i", [self numberOfRowsInSection:0]); just after you call beginUpdates and just before calling endUpdates. Also, call NSLog(@"inserting index paths: %@", insertIndexPaths) when informing the UITableView of an insert operation. My guess is that self.messenges accurately reflects the rows that should be in the table, but by adding +1 when self.tableView.editing is true, you push the numberOfRowsInSection: above what you report in your calls to insertRowsAtIndexPaths:withRowAnimation:.

Try this:

-(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 row index above to exclude the +1 in it. I changed the section index to be 0, which should be correct unless this UITableView does in fact have multiple sections that haven't been mentioned. Make sure that your logs make sense. If you see numberOfRowsInSection: increase by two during the course of insertRows: then you had also better see insertRowsAtIndexPaths: reflect the same thing. I'm confused why you need the editing flag to add another row in your numberOfRowsInSection: method. This part (where you add an additional row if self.editing is set) doesn't seem to be working right. Perhaps just try leaving out this part to have some of it working, then add it back in once you have some things working properly. Perhaps change numberOfRowsInSection: to be like the following until some things begin 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;
}
Sam
  • 26,946
  • 12
  • 75
  • 101
  • Used your code. But I still have the same error. Maybe we have forgotten something? – RomanHouse Apr 01 '12 at 20:25
  • 1
    I would look at two things, 1.) in your `insertRows:` method you need to update the same index in `self.messenges` and your call to `insertRowsAtIndexPaths:withRowAnimation:`. At moment you add to end of `self.messenges` but you pass row index of `1` when you call `insertRowsAtIndexPaths:withRowAnimation:`. 2.) Make sure you are properly adding / removing a row when changing your `self.editing` variable as this effects what is returned in `numberOfRowsInSection:`. I've updated my answer with more detail. – Sam Apr 01 '12 at 22:52
  • 1
    The error you saw was b/c you tried inserting into `self.messenges` beyond the end of it. Example, if you have a count of 2 into array `self.messenges` you can insert using an index between 0 and 2. 0 will be before any other item, 1 just after first item, and 2 (count) will be after second item (i.e. it will insert to end). I amended my answer with more details. Hope things start working for you. – Sam Apr 02 '12 at 14:41
  • Can we talk in the chat? I have some questions for your code. – RomanHouse Apr 02 '12 at 21:21