7

I'm building my first real app, and you guys has already been most helpful, but I've been struggling with the tableView not updating. I hope that someone here could shed some light over this, and maybe come up with any ideas on other stuff i might have missed or done unnecessarily complicated.

For now, the app is supposed to do the following:

  1. Fill table with data from the sqlite db (basically just list all tables)
  2. User presses button (which triggers fetchData method)
  3. (void)fetchData compares local db to server db checksums
  4. (void)fetchData sets up a NSURLConnection, communicating with the server thru POST
  5. When connection has received all data, it updates the local db
  6. The tableView dataSource (which is a NSMutableArray) is updated with the new db data
  7. The tableview gets updated and lists all tables in the database [myTableView reloadData] is called

Things I've double checked: connections between tableview and delegate, datasource and outlet; that the tableView is not nil; that the datasource itself has been accurately updated;

Everything works up to that last point (7). It seems that myTableView is not nil at the time. Ideas? And please tell me if you would need to see more of my code.

I've also looked a bit at the methods beginUpdates and endUpdates, but it seems to me that they focuses on a few changes at a time and user interactivity. I would like to just reload the entire table based on user choices (i.e. would like to reflect a whole other SQL select string depending on the current user login). Or is there another, even better way to do that?

Thanks in advance!

Here's a rather good chunk of the code:

#import "FirstViewController.h"
@interface FirstViewController ()
@end

@implementation FirstViewController
#import "FirstViewController.h"

@synthesize myTextView, myTableViewDataSource, myFetchedData, resultat, tablesAndChecks, tablesArr, checksArr, tablesToRequest,receivedData, receivedDataString, SQL, sqlStatementsArr, failedSqlStatementsArr, failedSqlStatementsCodeArr, dbloop1;



#pragma mark Table view methods
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    NSLog(@"Konfigurerar tableView"); //swedish for "Configuring tableView"
    if(myTableView.dataSource==nil){
        NSLog(@"datasource = nil");
    }else{
        NSLog(@"datasource != nil"); //This prints in log
    }
    NSLog(@"%d",[myTableViewDataSource count]); //prints "19" in log

    return [myTableViewDataSource count];
}

// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    NSLog(@"calling cellForRowAtIndexPath"); //This does NOT print in log

    static NSString *CellIdentifier = @"myCell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    }

    // Set up the cell...
    cell.textLabel.font = [UIFont fontWithName:@"Helvetica" size:15];
    cell.textLabel.text = [NSString  stringWithFormat:@"Tabell %d: %@", [indexPath row], [myTableViewDataSource objectAtIndex:[indexPath row]]];

    return cell;
}



- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    NSLog(@"connection did finish loading");

    //...
    //script that receives a long SQL-string from the server
    //and then updates the database with it goes here    
    //...

    //Ok, so now the database has updated correctly, and it's time 
    //to update the tableview so that it reflects the new data.
    //
    //Get data from database...
    NSMutableArray * tempArray = [[NSMutableArray alloc] initWithCapacity:0];

    if (sqlite3_open(dbpath, &contactDB) == SQLITE_OK)
    {
        NSLog(@"Databasen öppnad");
        NSString *beginSQL = [NSString stringWithFormat: @"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;"];
        const char *begin_stmt = [beginSQL UTF8String];
        sqlite3_prepare_v2(contactDB, begin_stmt, -1, &statement, NULL);
        while(sqlite3_step(statement) == SQLITE_ROW) {
            char *col1 = (char *)sqlite3_column_text(statement, 0);
            if (col1 !=NULL){
                [tempArray addObject:[NSString stringWithUTF8String: col1]];
            }
        }
        if (sqlite3_step(statement) == SQLITE_DONE)
        {
            NSLog(@"#####%s;   Done: %@", sqlite3_errmsg(nil), beginSQL);
        } else {
            NSLog(@"#####%s;   Error with string: %@; Errcode: %d Errmsg: %s", sqlite3_errmsg(nil), beginSQL, sqlite3_errcode(contactDB), sqlite3_errmsg(nil));
        }

        NSLog(@"%d", sqlite3_finalize(statement));
        sqlite3_close(contactDB);

        //update the datasource to the values of tempArray
        myTableViewDataSource = tempArray;

        //Some logging to see that the updated data is in the array... (which it is)
        NSLog(@"myTableViewDataSource count: %d",[myTableViewDataSource count]);

        for (i=0; i<[myTableViewDataSource count]; i++) {
            NSLog(@"%@",[myTableViewDataSource objectAtIndex:i]);
        }
    }

    //Finally, check so that myTableView isn't nil and can receive messages
    if(myTableView == nil){
        NSLog(@"NILCHECK mytableview is nil!");
    }else{
        NSLog(@"NILCHECK mytableview is NOT nil!"); //This is printed out to the log
    }

    //Reload data -> nothing happens...
    [myTableView reloadData];
}


- (void)fetchData
{
    //function that gets current checksums for all db tables on the server, compares them to the local database
    //and then requests an SQL-string from the server to update the tables that needs it.
    //I don't think this is relevant for my problem, and it runs fine anyways.

    //The last thing it does is setting up a NSURLConnection to communicate with 
    //the server (sending which tables to request via POST and then getting the SQL-string as the server response)

        NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:request delegate:self];
        if (theConnection) {
            //receivedData = [NSMutableData data];
            NSLog(@"ReceivedData är: %d", [receivedData length]);
        } else {
            NSLog(@"Connection Failed!");
        }

        //All done here, now the ReceivedData method takes over

    }
}

Update:

This is my viewDidLoad method:

- (void)viewDidLoad
{
    //creates database and fills "tempArray" with data

        NSLog(@"%d", sqlite3_finalize(statement));
        sqlite3_close(contactDB);

        myTableViewDataSource = tempArray;
        NSLog(@"myTableViewDataSource count: %d",[myTableViewDataSource count]);

        for (i=0; i<[myTableViewDataSource count]; i++) {
            NSLog(@"%@",[myTableViewDataSource objectAtIndex:i]);
        }

    }

    myTableView = [[UITableView alloc] initWithFrame:self.view.frame style:UITableViewStylePlain];
    myTableView.delegate = self;   //Added this after comment (1)
    myTableView.dataSource = self; //Added this after comment (2)
}

Update: After adding this:

myTableView.delegate = self;
myTableView.dataSource = self;

to viewDidLoad, the tableView:numberOfRowsInSection: is called, and dataSource is not nil, but tableView:cellForRowAtIndexPath: is not called, and the table is not updated.

Update 2: Here's my header file:

    //
//  FirstViewController.h
//  OHBSYS Storyboards
//
//  Created by David Forsberg on 2012-09-25.
//  Copyright (c) 2012 David Forsberg. All rights reserved.
//

#import <UIKit/UIKit.h>
#import <sqlite3.h>

@interface FirstViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>{
    NSString *databasePath;
    sqlite3 *contactDB;

    UITextView *myTextView;
    UITableView *myTableView;

    NSMutableArray *myTableViewDataSource;

    NSMutableString * tableRowCount;
    NSMutableArray * dbloop1;

    //And a bunch of other variables here
}

@property (retain, nonatomic) IBOutlet UITextView *myTextView;
@property (nonatomic, retain) IBOutlet UITableView *myTableView;
@property (nonatomic, retain) IBOutlet NSMutableArray *myTableViewDataSource;

- (IBAction) fetchData;

@property (nonatomic) NSString * tableRowCount;
@property (nonatomic, retain) NSMutableArray * dbloop1;

//A bunch of other properties here as well

@end

Update 3:

I've tried checking the values using breakpoints.

Breakpoint inside tableview:numberofrowsinsection:

(lldb) po self.myTableView
(UITableView *) $0 = 0x0d13ca00 <UITableView: 0xd13ca00; frame = (0 20; 768 1004); clipsToBounds = YES; layer = <CALayer: 0x2a3d30>; contentOffset: {0, 0}>
(lldb) po self.myTableView.delegate
(objc_object *) $1 = 0x0ce99500 <FirstViewController: 0xce99500>
(lldb) po self.myTableView.dataSource
(objc_object *) $2 = 0x0ce99500 <FirstViewController: 0xce99500>
(lldb) po self.myTableViewDataSource
(NSMutableArray *) $3 = 0x0029fb20 <__NSArrayM 0x29fb20>(
CONTACTS,
sqlite_sequence
)

(lldb) 

Breakpoint after the db is updated, right before reloadData is called:

(lldb) po self.myTableView
(UITableView *) $4 = 0x0d13ca00 <UITableView: 0xd13ca00; frame = (0 20; 768 1004); clipsToBounds = YES; layer = <CALayer: 0x2a3d30>; contentOffset: {0, 0}>
(lldb) po self.myTableView.delegate
(objc_object *) $5 = 0x0ce99500 <FirstViewController: 0xce99500>
(lldb) po self.myTableView.dataSource
(objc_object *) $6 = 0x0ce99500 <FirstViewController: 0xce99500>
(lldb) po self.myTableView.dataSource //(accidentally hit that twice)
(objc_object *) $7 = 0x0ce99500 <FirstViewController: 0xce99500>
(lldb) po self.myTableViewDataSource
(NSMutableArray *) $8 = 0x0ce6baa0 <__NSArrayM 0xce6baa0>(
CONTACTS,
meta_tablechecksums,
prot_multicoltest,
prot_multicoltest_4,
prot_multicoltest_4_desc,
prot_multicoltest_desc,
sqlite_sequence,
superadmin_filefolders,
superadmin_files,
superadmin_imagefolders,
superadmin_images,
sys_customers,
sys_fieldlooks,
sys_fieldtypes,
sys_formtables,
sys_pages,
sys_subpages,
sys_userroles,
sys_users
)

(lldb) 

 

Breakpoint AT reloadData line:

(lldb) po self.myTableView
(UITableView *) $9 = 0x0d13ca00 <UITableView: 0xd13ca00; frame = (0 20; 768 1004); clipsToBounds = YES; layer = <CALayer: 0x2a3d30>; contentOffset: {0, 0}>
(lldb) po self.myTableView.delegate
(objc_object *) $10 = 0x0ce99500 <FirstViewController: 0xce99500>
(lldb) po self.myTableView.dataSource
(objc_object *) $11 = 0x0ce99500 <FirstViewController: 0xce99500>
(lldb) po self.myTableViewDataSource
(NSMutableArray *) $12 = 0x0ce6baa0 <__NSArrayM 0xce6baa0>(
CONTACTS,
meta_tablechecksums,
prot_multicoltest,
prot_multicoltest_4,
prot_multicoltest_4_desc,
prot_multicoltest_desc,
sqlite_sequence,
superadmin_filefolders,
superadmin_files,
superadmin_imagefolders,
superadmin_images,
sys_customers,
sys_fieldlooks,
sys_fieldtypes,
sys_formtables,
sys_pages,
sys_subpages,
sys_userroles,
sys_users
)

(lldb) 

Breakpoint inside tableview:numberofrowsinsection: after the db was updated and the reloadData command was run:

(lldb) po self.myTableView
(UITableView *) $13 = 0x0d13ca00 <UITableView: 0xd13ca00; frame = (0 20; 768 1004); clipsToBounds = YES; layer = <CALayer: 0x2a3d30>; contentOffset: {0, 0}>
(lldb) po self.myTableView.delegate
(objc_object *) $14 = 0x0ce99500 <FirstViewController: 0xce99500>
(lldb) po self.myTableView.dataSource
(objc_object *) $15 = 0x0ce99500 <FirstViewController: 0xce99500>
(lldb) po self.myTableViewDataSource
(NSMutableArray *) $16 = 0x0ce6baa0 <__NSArrayM 0xce6baa0>(
CONTACTS,
meta_tablechecksums,
prot_multicoltest,
prot_multicoltest_4,
prot_multicoltest_4_desc,
prot_multicoltest_desc,
sqlite_sequence,
superadmin_filefolders,
superadmin_files,
superadmin_imagefolders,
superadmin_images,
sys_customers,
sys_fieldlooks,
sys_fieldtypes,
sys_formtables,
sys_pages,
sys_subpages,
sys_userroles,
sys_users
)

(lldb) 
Daniyar
  • 2,975
  • 2
  • 26
  • 39
davidanton1d
  • 319
  • 3
  • 10
  • Is this `Konfigurerar tableView` message shown? – Tomasz Wojtkowiak Oct 08 '12 at 19:28
  • 1
    Where do you set the dataSource of you tableview? – yinkou Oct 08 '12 at 19:48
  • 1
    in `viewDidLoad` add `myTableView.dataSource = self;` – lnafziger Oct 08 '12 at 19:53
  • Thanks @lnafziger, after adding that line the `konfigurerar tableView` message is shown, but the table is still not updating. – davidanton1d Oct 08 '12 at 20:12
  • @tomaszwojtkowiak It is now, after adding `myTableView.dataSource = self;` to viewDidLoad, but the tableView isn't updating – davidanton1d Oct 08 '12 at 20:13
  • What does `tableView:numberOfRowsInSection:` return? If it is 0, `tableView:cellForRowAtIndexPath:` will not be called. – lnafziger Oct 08 '12 at 20:47
  • Add a breakooint in the reloaddata statement and in the console write po myTableView then write down what it did print... – Lefteris Oct 08 '12 at 23:22
  • 1
    Sorry did miss your update... Are you initializing the myTableDataSource mutable array before you actually start adding items to it? You need to allocate and init the object first! – Lefteris Oct 08 '12 at 23:26
  • @lnafziger By NSLog'ing the same var as `tableView:numberOfRowsInSection:` is returning, it prints `19`. – davidanton1d Oct 09 '12 at 11:01
  • @Lefteris I tried adding `myTableViewDataSource = [[NSMutableArray alloc] initWithCapacity:0];` in viewDidLoad, with no effect. But the array gets the objects i add, and it counts `19` when it's checked in `tableView:numberOfRowsInSection:`. It just seems that `tableView:cellForRowAtIndexPath:` doesn't get called after it! Why could that be? – davidanton1d Oct 09 '12 at 11:19
  • @Lefteris Okay now this was interesting! I added a breakpoint and wrote `po tableView`, and then i got this: `(UITableView *) $0 = 0x00000000 ` which means that myTableView is nil - even though i checked for that in the line over the `[myTableView reloadData]`! Why is that? And why does `numberOfRowsInSection` run if it's nil? – davidanton1d Oct 09 '12 at 11:25
  • Where did you add the breakpoint? Depending where you added it, you might want to actually print the myTableView object not the tableView, unless you did it inside in the datasource method of the table view – Lefteris Oct 09 '12 at 11:38
  • @Lefteris My bad. I was checking `po myTableView`, but the actual reference is nof `self.myTableView`. Now, when i check these: `myTableView, myTableView.delegate, myTableView.datasource, myTableViewDataSource` none of them are nil. `myTableViewDataSource` lists the contents of the array, and everything seems to be in order. I checked these with different breakpoints in viewDidLoad, in the end of the Connection-function and in the `numberOfRowsInSection`. I've added the responses in the question above. – davidanton1d Oct 09 '12 at 19:10
  • Since you are having your tableview as an outlet (which makes sense if you have a tableview connected in your xib), you do not need to alloc/init it in code, nor you have to set its delegate/datasource manually. Just connect the properties in your xib, set the relative protocols in your .h file and you should be fine. Otherwise you should be adding your tableview as a subview in your main view in order to present it on screen. I hope that this makes sense... – Alladinian Oct 09 '12 at 19:43

3 Answers3

2

I don't see the myTableView being synthesized. Also are the tableView datasource methods called, upon doing the reloadData ?

If not, double check and verify that the myTableView delegate is set correctly. If you have correctly connected the Table to the nib and you have an outlet you can set it up also in code, i.e. in the viewDidLoad method, by setting:

myTableView.delegate = self;
Lefteris
  • 14,550
  • 2
  • 56
  • 95
  • Read somewhere that UITableView doesn't need to be synthesized, so I removed it. Anyway, as you (now) can see I initiate it in the `viewDidLoad` method, which prevented myTableView from being `nil` at the time i called `reloadData`. Adding `myTableView.delegate = self;` doesn't seem to change anything. Am i missing anything in my tableView delegate methods perhaps? (Thanks for quick answer) – davidanton1d Oct 08 '12 at 19:44
  • The method `-(NSInteger)tableView:numberOfRowsInSection:` is not called upon `reloadData`. (should I check any other dataSource methods as well? Just happened to have a NSLog in that one) – davidanton1d Oct 08 '12 at 19:47
  • I'm with @Lefteris on this one. I can't see your header file. Did you declare a property on your view controller to hold a reference to the tableview? Direct ivar access is discouraged. I would expect to see `self.myTableView.delegate = self;`, for example. – Joshua Smith Oct 08 '12 at 21:00
2

@Lefteris was right. You may already have this but I can't see your header file. In the view controller's header file, add a property to maintain a reference to the tableview:

@property (nonatomic, strong) UITableView *myTableView;

Then in your implementation file:

@synthesize myTableView = myTableView_;

Finally, within the implementation file, change all references to the tableview from myTableView to self.myTableView and see if you suddenly start getting your delegate callbacks.

Joshua Smith
  • 944
  • 6
  • 10
  • I have now added my header file to the question. Yes, `myTableView` is declared as a property and then synthesized in the implementation file - but not as `@synthesize myTableView = myTableView_;`. Will try this, but would you be kind to explain the difference from `@synthesize myTableView;`? – davidanton1d Oct 09 '12 at 08:29
  • 1
    Sure. `@synthesize myTableView = myTableView_` creates getters and setters for a property named `myTableView`, but with a backing ivar named `myTableView_`. When you do it this way, it becomes a compiler error to access `myTableView` directly. You have to use `self.myTableView`. This lets you take advantage of some automatic memory management. See [this answer.](http://stackoverflow.com/a/1551976/294279) – Joshua Smith Oct 09 '12 at 13:16
  • 1
    Also, when using `@property` and `@synthesize`, you do not need to explicitly declare the backing ivars in your interface declaration in the header. That is, you can (and should) delete `UITableView *myTableView;` and the others, and just use properties. – Joshua Smith Oct 09 '12 at 13:23
0

The basic problem in this was a lack of a Controller in the MVC application. I read up on the basic concept and built it again from scratch - this time, the right way.

davidanton1d
  • 319
  • 3
  • 10