iPhone SDK: loading UITableView from SQLite - iphone

I think I got a good handle on UITableViews and on getting/inserting data from/to SQLite db. I am straggling with an architectural question.
My application saves 3 values int the database, there can be many/many rows. However would I load them in the table?
From all the tutorials I have seen, at one point entire database is loaded in the NSMutableArray or similar object via performing SELECT statement.
Then when
-(UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath
called, rows required are dolled out from the previous loaded NSMutableArray (or similar stracture).
But what i have have thousands for rows? Why would I pre-load them?
Should I just query database each time cellForRowAtIndexPath is called? If so, what would I use as an index?
Each row in the table will have an AUTOINCREMENT index, but since some rows may be deleted index will not correspond to rows in the table (in the SQL I may have something like this with row with index 3 missing):
1 Data1 Data1
2 Data2 Data2
4. data3 data3
Thanks

I solve this by reading what the table cell needs from my db into the datasource array, of all the existing db entries. The objects don't get removed from the array however, they stay there unless they need to be removed.
For example one of my apps reads 1'700 rows, for each row creates an object, assigns an NSUInteger (the autoincrement value) and an NSString (the name of the object, which will be displayed in the cell) and puts them into the datasource array. This whole process takes only about 200-300 milliseconds - you'll have to test whether it takes too long for 10'000+ entries, but for some thousand entries it sould be okay to just read it all. I remember that memory consumption is also quite low, can't look up how much exactly ATM.
Then, when the user taps a row, I query the datasource array to find the object he just tapped, and this object then loads all its other values from the database, which it can do since it knows its own database key/id (it "hydrates" itself).
For completeness sake (using FMDB):
NSResultSet *res = [db executeQuery:#"SELECT my_key, my_name FROM table"];
while ([res next]) {
NSDictionary *dict = [res resultDict];
MyObj *obj = [MyObj obj];
obj.id = [dict objectForKey:#"my_key"];
obj.name = [dict objectForKey:#"my_name"];
[datasourceArray addObject:obj];
}

Here is what I did, which worked perfectly (i have tested with 3k rows in the DB). The app is EZBudget
I read only indexes into the array, but I do so for entire table, however many rows it has
In the -(UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath in do SELECT on the indexPath as index only
If certain rows needs to be deleted (a transaction is deleted) I delete index from the array and I delete corresponding row from the table
When record is added in the table, I get SQLite returns the autoincremented index for the newly added record. I take this index and add to the index array
So my index array is always in sync with the entire table. At any point I can SELECT entire record by index in the array.
Thanks everyone for suggestions!

You can always do one select statement to fetch just the 'autoincrement indices' in the order you want them, and keep them in an array (you'll have an array with thousands of integers, should be fine). Then, in 'cellForRowAtIndexPath', get the right 'autoincrement index' from your array and fetch all the data you need (I'm calling 'id' your 'autoincrement index' below...).
NSArray *ids; // Put the result of 'select id from datatable' here
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Fetch full row where autoincrement index is: [ids objectAtIndex:indexPath.row]
Of course, you can even use a int[] (instead of NSArray* ) if you want to save on calls to intValue and such... You can allocate your int[] using the result of 'select count(*) from datatable'

Related

Is there a way to work with records that you marked in table on X++?

I want to work with table records that is selected (Marked), Example - I have 10 records in table and i mark 5 of them. Expected - when i work with selected records, it should look only on that 5 records, not whole 10.
So far i have this code that selects all records:
while select Table
where table.JournalId == table.JournalId
Is there a way to make it select only marked records, not everything?
That select is writen in class. I need to get those marked records into that class where that select is writen...
You need to pass the formdatasource object into the class that is selecting the records, and then use the MultiSelectionHelper to loop through the selected records on the formdatasource.
In the example below, the object salesTableFormDataSource needs to be passed in from the form to the class you are using. Obviously replace that with whatever your datasource/table needs are.
MultiSelectionHelper selection = MultiSelectionHelper::construct();
selection.parmDatasource(salesTableFormDataSource);
SalesTable salesTable = selection.getFirst();
while (salesTable)
{
//do something with your table buffer.
salesTable = selection.getNext();
}

My iPhone project doesn't fetch more than 5 rows of the sqlite table?

When I'm trying to fetch data from the sqlite database table the NSArray has a capacity of 100 and the count array has a capacity of 9. count[5] returns to me rubbish data which is not in the table at all. Even the first 5 records returned correctly.
if(sqlite3_open([dbPath UTF8String],&database)==SQLITE_OK){
const char* sql2= " select * from Bcars";
sqlite3_stmt *selectstatment;
if(sqlite3_prepare_v2(database,sql2,-1,&selectstatment,Nil)==SQLITE_OK){
while(sqlite3_step(selectstatment)==SQLITE_ROW){
// fetch the id
count[i++]=sqlite3_column_int(selectstatment, 0);
carobject.primarykey=sqlite3_column_int(selectstatment, 0);
[ar addObject:[NSString stringWithUTF8String:(char*)sqlite3_column_text(selectstatment, 1)]];
[ar1 addObject:[NSString stringWithUTF8String:(char*)sqlite3_column_text(selectstatment, 2)]];
}
}
}
else
{
sqlite3_close(database);
self.statustext.text=#" database closed";
}
self.statustext.text=[[NSString alloc]initWithFormat:#"%d",count[4]];/* when I try to return count[5] it gives me rabbish value !!*/
self.searchtext.text=(NSString*)[ar objectAtIndex:5];//here is an error occurred !!
There are a couple of issues with your code:
You have not initialized the i counter so I presume that it has been initialized outside the snippet, or?
How do you know how much data is actually available in the database? The count array has to be large enough to hold all the rows or you will overwrite some unallocated memory. Maybe you know this beforehand or else you should find out e.g. by executing some SQL statement aka "SELECT COUNT(*) FROM Bcars".
Are you absolutely sure that the database holds more than 5 entries? The code just assumes this, but doesn't make any checks on the value of i or [ar count] whether you actually received these entries?
Make sure you clean up after execution by calling sqlite3_finalize()

text search too slow on sqlite db on tableview on iphone

I have a large table of around 2500 entries. I am displaying it on tableview. however the search bar is too slow while doing dynamic search. ie I am filtering the table everytime the user puts in a character on search bar.
following is the code:
- (void)searchBar:(UISearchBar *)theSearchBar textDidChange:(NSString *)searchText {
if([searchText length] > 0) {
searching = YES;
letUserSelectRow = YES;
self.tableView.scrollEnabled = YES;
[self searchTableView];
} else {
searching = NO;
letUserSelectRow = NO;
self.tableView.scrollEnabled = NO;
[whereClause setString: #"%%"];
}
[self.tableView reloadData];
}
- (void) searchTableView {
NSString *searchText = searchBar.text;
[whereClause setString: #"%%"];
[whereClause appendString: searchText];
[whereClause appendString: #"%%"];
[self.tableView reloadData];
}
the whereClause is there in the sqlite query, so it keeps appending the search character. When the user types in the keyboard keys become quite sticky and slow to type. Any suggestion will be appreciated...
SQLite's selects are usually very fast. If you posted some sample queries, it'd probably be easier to help you.
However, that said: If you have a query with a simple WHERE clause that's performing slowly, you probably don't have an index on the column that's being searched. Add one.
2500 entries in a table is NOT large for SQLite. 25,000,000 would be large.
When you're working with SQLite, you can test a lot of things on the desktop using the sqlite3 shell. For instance, if your query is something like:
SELECT * FROM MyTable WHERE Column='A';
You can do something like this:
EXPLAIN QUERY PLAN SELECT * FROM MyTable WHERE Column='A';
If you see output like this:
0|0|TABLE MyTable
It means SQLite is crawling the entire table to get the results.
In this case, adding an index would help:
CREATE INDEX MyTableColumn ON MyTable(Column);
Then, the explain above will give you something like this instead:
0|0|TABLE MyTable WITH INDEX MyTableColumn
If your WHERE clause is more complicated, you might need to make a compound index instead, since SQLite will only use one index per table.
CREATE INDEX MyTableFirstSecond ON MyTable(First,Second);
So in your case:
Get your database onto the Mac.
Use the shell to diagnose which of your queries are slow.
Study the queries to see why they're slow: What needs to be indexed? And less likely: What about the query is awkward, preventing the query optimizer from picking a sensible plan?
Add the appropriate indexes or tune the query.
Re-test to make sure the change works.
The rule of thumb with SQLite is simple: If you're going to search on it, it should be indexed.
After the first character, copy the resulting data into an array and then filter the array using a predicate. It's much faster. Then reload your table from the array. The array might be an array of dictionaries where each element is a dictionary with the search string (i.e. name or whatever) and the other entry is a reference to the core data entity that you want as the final selection. Or you can just so the core data selection at the end when the user makes a selection.
Note that when you filter with a predicate, the objects in the array need to have properties that match what you are searching for. In my example, I created an object that had properties fullName emailAddressString etc..
predicate = [NSPredicate predicateWithFormat:#"fullName contains[cd] %# or emailAddressString contains[cd] %#", searchString, searchString];
NSArray *resultArray = [[NSArray alloc] initWithArray:[allContacts filteredArrayUsingPredicate:predicate]];

how do i add a new row on the last row of uitableview

Instead of having a '+' sign to insert a new row..is there a way whereby i can insert a new row on my existing table?meaning to display the "Insert new row" on the last row of my uitableview..and also is it possible to edit the string to different name rather then just "Insert new row".
Sure,
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
In the DataSource just create extra cell for the last index. You will also need to make sure that:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
Returns +1 to account for the 'Insert Cell'.
When this cell is selected just modify your underlying model to create a new object and reload the table.

Best way to reference image through out app?

My application is database driven. Each row contains the main content column I display in a UIWebView. Most of the rows (the content column) have a reference to image1 and some to image2. I convert these to base64 and add the image string into the row. However, if either image changes, it means I have to go back through all the rows and update the base64 string.
I can provide a unique string in the row content such as {image1}. It means I'll have to search through the entire content for that row and replace with the base64 version of the image. These images are also always at the bottom of the row content. Not sure how having to go through all content first before replacing will affect performance. Is there a better way to do this?
I hope I am understanding your question correctly.
If the images are not very large, then it is probably OK to just use the like keyword as in:
sqlite3_stmt *statement = nil;
if(statement == nil)
{
const char *sql = [[NSString stringWithFormat:#"SELECT imageContent FROM imageDatabase WHERE imageContent LIKE '%#%#%#'", #"%", imageValue, #"%"] UTF8String];
if (sqlite3_prepare_v2(db, sql, -1, &statement, NULL) != SQLITE_OK) {
//NSAssert1(0, #"Error: failed to prepare statement with message '%s'.", sqlite3_errmsg(db));
return;
}
}
while (sqlite3_step(statement) == SQLITE_ROW) {
// query was successful
// perform some action on the resulting data
}
sqlite3_finalize(statement);
statement = nil;
If you set imageValue = image1, image2, or whatever, that will give you the item you are looking for from the database without having to do string manipulation in code. I am assuming you know SQL, so sorry if this is redundant information, but the above will search your imageDatabase for anything that contains the image1, image2 imageValue. Once you find the row, you can update it, or you can use the WHERE clause with the UPDATE SQL statement, but I find that to be a bit dangerous due to the possibility of inadvertently updating multiple rows without checking the content first to make sure it is what you want.
Also, if you are doing database updates with this, you will find a major performance boost by wrapping your inserts and updates with transactions like:
const char *sql = "BEGIN TRANSACTION;";
char *errMsg;
sqlite3_exec(db, sql, nil, 0, &errMsg);
const char *commit = "COMMIT;";
sqlite3_exec(db, commit, nil, 0, &errMsg);
It prepares and optimizes your query before executing it. I have seen insert and update queries get twice as fast with transactions.
If the database is very large, this will have a significant performance hit, but doing the string manipulation in memory will have a large memory cost. If you use the SQLite LIKE method, the string search is done in a serial fashion on disk and has less of a memory hit.
Once you have found the specific item you can do the regular expression search and replace on just that particular string keeping your code's memory footprint smaller.
Why not have the images in a table, with image_ID (a unique integer) and image_data (a blob)? Then in your main table, store just the image_ID, and do a join if you need the actual image?
On an alternative interpretation of your question (if that answer doesn't mnake sense to you) why not break the content into three fields: stuff before the image, the image, and the stuff after. Store the image_ID for the middle part (not the data--get that with an sql JOIN on the image table). Then build the final content with concatenation.