Save and retrieve AttributedString to SQLite3 - objective-c

I have a project in Objective-c in which I am trying to find a way of saving the attributedText from a UITextView to a SQLite3 table.
My Project Target OS is 12.1.
I am using an object called "MMItem" with a NSData property called "notesAttributed".
In my viewController Class I am using NSKeyedArchiver to encode the AttributedText into a NSdata format then copying to the object property.
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self.itemNotes.attributedText requiringSecureCoding:YES error:Nil];
self.item.notesAttributed = data;
I then call a method in my model to save the object
NSString *resultStr = [self.meetingModel saveAttributedItemNote:item];
In the model I'm attempting to save the attributed string to a field in the Item table setup as type 'blob'
- (NSString *)saveAttributedItemNote:(MMItem *)item{
NSString *errorMessage;
NSString *sql;
NSInteger result = 0;
if (item) {
//create blob encoded data for Attributed notes
NSInteger itemID = item.itemID;
sql = [NSString stringWithFormat:
#"UPDATE Item set noteAttributed = %# WHERE itemID = %ld",item.notesAttributed, (long)itemID];
char *err;
// open DB and save
if ([self openDB]){
//NSLog(#"%#", NSStringFromSelector(_cmd));
result = sqlite3_exec(db, [sql UTF8String], NULL, NULL, &err);
sqlite3_close(db);
}
if (result != SQLITE_OK)
{
errorMessage = #"Error";
}
else
{
//NSLog(#"Table updated");
errorMessage = #"OK";
[self saveAttributedItemNote:item];
}
}
item = nil;
return errorMessage;
}
The SQL Execute statement fails with error 1.
I suspect I'm meant to insert the blob into the table using 'binding' and not an Execute statement but I just find how to put this all together using Objective-c.
Any help would be great thanks.

With acknowledgement to the following post iOS SQLite Blob data is saving NULL
I managed to get a working solution. It's apparent that to save a blob into an SQLite you must use a Prepare statement with a sqlite3_bind_blob to insert the blob parameters into the statement, then to use sqlite3_step to deploy it.
This then allows the bytes and length parameters to also be passed into the statement, which I don't think can be done using the execute method I was originally trying.
Here is the code that works perfectly.
- (NSString *)saveAttributedItemNote:(MMItem *)item{
NSString *errorMessage;
if (item) {
//create blob encoded data for Attributed notes
int itemID = (int)item.itemID;
if ([self openDB]) {
const char *insert_stmt = "UPDATE Item set notesAttributed = ? WHERE itemID = ?";
sqlite3_stmt *statement;
if (sqlite3_prepare_v2(db, insert_stmt, -1, &statement, NULL) == SQLITE_OK) {
sqlite3_bind_blob(statement, 1, item.notesAttributed.bytes, (int)item.notesAttributed.length, SQLITE_TRANSIENT);
sqlite3_bind_int(statement, 2, itemID);
if (sqlite3_step(statement) == SQLITE_DONE) {
errorMessage = #"OK";
//update successfull
}else{
const char *err = sqlite3_errmsg(db);
NSString *error = [[NSString alloc] initWithUTF8String:err];
NSLog(#"Update error ID:%#",error);
errorMessage = #"Error";
}
sqlite3_finalize(statement);
}else{
errorMessage = #"Error";
NSLog(#"Unable to prepare stsement %s: %s",insert_stmt, sqlite3_errmsg(db));
}
sqlite3_close(db);
}
}
item = nil;
return errorMessage;
}
The following code then shows how the field was retrieved from sqlite. Column 11 is the blob.
if (sqlite3_prepare(db, [sql UTF8String], -1, &statement, nil)==SQLITE_OK)
{
//NSLog(#"SQL Select OK");
while (sqlite3_step(statement)==SQLITE_ROW) {
MMItem *item = [[MMItem alloc]init];
item.itemID = sqlite3_column_int(statement, 0);
item.topicID = sqlite3_column_int(statement, 1);
item.sequenceID = sqlite3_column_int(statement, 2);
char *description = (char *) sqlite3_column_text(statement, 3);
if(description) item.itemDescription = [[NSString alloc]initWithUTF8String:description];
char *notes = (char *) sqlite3_column_text(statement, 4);
if(notes) item.notes = [[NSString alloc]initWithUTF8String:notes];
char *actionBy = (char *) sqlite3_column_text(statement, 5);
if(actionBy) item.actionBy = [[NSString alloc]initWithUTF8String:actionBy];
char *requiredBy = (char *) sqlite3_column_text(statement, 6);
if (requiredBy) item.requiredBy = [[NSString alloc]initWithUTF8String:requiredBy];
item.completed = sqlite3_column_int(statement, 7);
char *proposedBy = (char *) sqlite3_column_text(statement, 8);
if (proposedBy) item.proposedBy = [[NSString alloc]initWithUTF8String:proposedBy];
char *secondedBy = (char *) sqlite3_column_text(statement, 9);
if (secondedBy) item.secondedBy = [[NSString alloc]initWithUTF8String:secondedBy];
item.carried = sqlite3_column_int(statement, 10);
NSData *attributedTextData = [[NSData alloc]initWithBytes:sqlite3_column_blob(statement,11) length:sqlite3_column_bytes(statement, 11)];
item.notesAttributed = attributedTextData;
[items addObject:item];
item = nil;
}
}
Then in the ViewController, the following was used to take the NSData property and decode this for the UITextView (self.itemNotes)
self.itemNotes.attributedText = [NSKeyedUnarchiver unarchivedObjectOfClass:([NSAttributedString class]) fromData:self.item.notesAttributed error:&error];
Weird how just posting the question lead me to finding the right solution. Thanks to El Tomato for your help bud.

Related

how to retrieve sqlite column values and store in to the array in objective c?

Here is my code i am getting only the last value of the database in the array but i need the entire column values in my array...
//select the values from the db
NSString *sql = [NSString stringWithFormat:#"SELECT * FROM district_details"];
sqlite3_stmt *statement;
if (sqlite3_prepare_v2(db, [sql UTF8String], -1, &statement, nil)==SQLITE_OK)
{
while (sqlite3_step(statement)==SQLITE_ROW)
{
char *field2 = (char *)sqlite3_column_text(statement, 1);
field2Str = [[NSString alloc]initWithUTF8String:field2];
str = [NSString stringWithFormat:#"%#", field2Str]; NSLog(#"the value from thr field1 from the district name is %#...",field2Str);
myarray =[[NSMutableArray alloc]initWithObjects:#"--select the value--",nil];
[myarray addObject:field2Str];
NSLog(#"the value of the myarray is......%#",myarray);
}
}
Initiate your array object once before you use it, if you initiate it inside while loop like you do it loses the previous added objects and takes fresh allocation inside the heap memory.
Just put this code inside viewDidLoad method and remove from while loop:
- (void)viewDidLoad {
[super viewDidLoad];
// mutable array allocation do once here
myarray =[[NSMutableArray alloc]initWithObjects:#"--select the value--",nil];
}
Now use below code to fetch entire column from data base:
//select the values from the db
NSString *sql = [NSString stringWithFormat:#"SELECT * FROM district_details"];
sqlite3_stmt *statement;
if (sqlite3_prepare_v2(db, [sql UTF8String], -1, &statement, nil)==SQLITE_OK)
{
while (sqlite3_step(statement)==SQLITE_ROW)
{
char *field2 = (char *)sqlite3_column_text(statement, 1);
field2Str = [[NSString alloc]initWithUTF8String:field2];
// --------- myarray allocation removed from here -----------
[myarray addObject:field2Str];
NSLog(#"the value of the myarray is......%#",myarray);
}
sqlite3_finalize(statement);
}
sqlite3_close(db);
The problem is that you are instantiating a new mutable array in ever loop of your while statement. You want to instantiate it once, before the loop, and inside the loop merely addObject.
NSString *sql = [NSString stringWithFormat:#"SELECT * FROM district_details"];
sqlite3_stmt *statement;
if (sqlite3_prepare_v2(db, [sql UTF8String], -1, &statement, nil)==SQLITE_OK) {
myarray = [[NSMutableArray alloc] initWithObjects:#"--select the value--",nil];
while (sqlite3_step(statement)==SQLITE_ROW) {
char *field2 = (char *)sqlite3_column_text(statement, 1);
field2Str = [[NSString alloc] initWithUTF8String:field2];
[myarray addObject:field2Str];
}
sqlite3_finalize(statement); // remember to finalize to prevent SQLite leak
}

Xcode - NSInvalidArgumentException', reason: '*** +[NSString stringWithUTF8String:]: NULL cString'

I have the below code that should populate the TableViewController with information from my sqlite file, it lets me add fine, and i can view the file and the information is there, but I'm getting the above error message, and failing miserably at fixing it....
-(NSMutableArray *) stockList
{
NSString *filePath = [self getWritableDBPath];
if(sqlite3_open([filePath UTF8String], &db) == SQLITE_OK)
{
const char *sql = "Select Description, Quantity from StockTable";
sqlite3_stmt *sqlStatement;
if(sqlite3_prepare(db, sql, -1, &sqlStatement, NULL) != SQLITE_OK)
{
NSLog(#"Problem with query: %s", sqlite3_errmsg(db));
}
else
{
while (sqlite3_step(sqlStatement)==SQLITE_ROW)
{
Stock * stock = [[Stock alloc] init];
stock.desc = [NSString stringWithUTF8String:(char *) sqlite3_column_text(sqlStatement, 1)];
stock.qty = [NSString stringWithUTF8String:(char *) sqlite3_column_text(sqlStatement, 2)];
[thestock addObject:stock];
stock = nil;
}
}
sqlite3_finalize(sqlStatement);
}
sqlite3_close(db);
return thestock;
}
thanks for any help, currently googling it myself..
connection strings as mentioned below: (reason being it causes a LINK error and states that MyDB is a duplicate in both views)
TableView:
NSString * MyDB2=#"StockDatabase.db";
AddingView:
NSString * MyDB=#"StockDatabase.db";
One (or both) of the columns you are fetching from the database is NULL:
stock.desc = [NSString stringWithUTF8String:(char *)sqlite3_column_text(sqlStatement, 1)];
stock.qty = [NSString stringWithUTF8String:(char *)sqlite3_column_text(sqlStatement, 2)];
Guard against that with:
const char *desc = sqlite3_column_text(sqlStatement, 1);
if (desc)
stock.desk = #(desc);
const char *qty = sqlite3_column_text(sqlStatement, 2);
if (qty)
stock.qty = #(qty);
I got this error too. How I solved it is by setting the first column text index to "0" instead of "1". And the error went away.
char *charPrice = (char*) sqlite3_column_text(stmt, 0);
NSString *price = [NSString stringWithUTF8String:charPrice];
char *charName = (char*) sqlite3_column_text(stmt, 1);
NSString *name = [NSString stringWithUTF8String:charName];

query sqlite error out of memory xcode

I have an error. Problem with prepare statement: out of memory.
How to fix it?
Other queries are working normally. I do not know what can be.
Maybe this is a problem -(TelefonDetail *)telefonDetails:(int)iDMob ??
-(TelefonDetail *)telefonDetails:(int)iDMob
{
TelefonDetail *retvalTelefon = nil;
NSString *ZaprosTelefons = #"SELECT iDMob , marka, model,wifi, os, razmeri,Display,Camera,Stoimos,imageTel,opisCrat FROM MobTele WHERE iDMob=1";
sqlite3_stmt *statement1;
if (sqlite3_prepare_v2(_database, [ZaprosTelefons UTF8String], -1, &statement1, nil)
!= SQLITE_OK) {
NSLog(#"Problem with prepare statement: %s", sqlite3_errmsg(_database));
}
else{ while (sqlite3_step(statement1) == SQLITE_ROW) {
int iDMob = sqlite3_column_int(statement1, 0);
char *Marka = (char *) sqlite3_column_text(statement1, 1);
char *Model = (char *) sqlite3_column_text(statement1, 2);
char *Wifi = (char *) sqlite3_column_text(statement1, 3);
char *OS = (char *) sqlite3_column_text(statement1, 4);
char *Razmeri = (char *) sqlite3_column_text(statement1, 5);
char *Display = (char *) sqlite3_column_text(statement1, 6);
char *Camera = (char *) sqlite3_column_text(statement1, 7);
char *Stoimos = (char *) sqlite3_column_text(statement1, 8);
Byte *imgTel = (Byte *) sqlite3_column_blob(statement1, 9);
NSString *marka = [[NSString alloc] initWithUTF8String:Marka];
NSString *model = [[NSString alloc] initWithUTF8String:Model];
NSString *wifi = [[NSString alloc] initWithUTF8String:Wifi];
NSString *os = [[NSString alloc] initWithUTF8String:OS];
NSString *razmeri = [[NSString alloc] initWithUTF8String:Razmeri];
NSString *display = [[NSString alloc] initWithUTF8String:Display];
NSString *camera = [[NSString alloc] initWithUTF8String:Camera];
NSString *Stoimost = [[NSString alloc] initWithUTF8String:Stoimos];
int len = sqlite3_column_bytes(statement1, 9);
NSData *imgData = [[NSData alloc] initWithBytes:imgTel length:len];
retvalTelefon = [[TelefonDetail alloc]initWhithIDMob:iDMob marka:marka model:model wifi:wifi os:os razmeri:razmeri display:display camera:camera Stoimost:Stoimost imegeTel:imgData];
}
sqlite3_finalize(statement1);
}
return retvalTelefon;
}
The "out of memory" error is often a misleading error message caused by trying to use a database without having opened it first (or if you accidentally set the sqlite3 database pointer to NULL). For example:
sqlite3 *db = NULL;
sqlite3_stmt *statement;
int rc;
// deliberately did not open database -- ERROR
// now try to use SQLite without opening database
if ((rc = sqlite3_prepare_v2(db, "select * from test", -1, &statement, NULL)) != SQLITE_OK)
NSLog(#"rc=%d errmsg=%s", rc, sqlite3_errmsg(db));
This will generate a return code (rc) of 21, SQLITE_MISUSE. And the error message is a misleading "out of memory":
2014-05-11 17:36:22.035 MyApp[19942:60b] rc=21 errmsg=out of memory

unable to save image in SQLite in iPhone

can anybody tell me why i am unable to insert an image in sqlite. Some time i get error like:
1)(sqlite3_step(stm) == SQLITE_DONE) , getting value as 21.
2)gets stored in an array and not into databse.
I a fresher in objective c so if u could post back a code it would be sweet, as i searched almost evry site.
Thanks!
-(IBAction)submitDetails:(id)sender
{
sqlite3_stmt *stm;
const char *dbpath = [databasePath UTF8String];
if (sqlite3_open(dbpath, &connectDB) == SQLITE_OK)
{
UIImage *contactImage = imageView.image;
NSData *imageData = UIImageJPEGRepresentation(contactImage, 100);
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:imageData forKey:#"image"];
[defaults synchronize];
NSLog(#"Data saved");
NSString *insertSQL = [NSString stringWithFormat: #"INSERT INTO STUDENTS (name,salary, gid,photo) VALUES (\"%#\", \"%#\", \"%#\",?)", name.text,salary.text,gid.text,imageData] ;
const char *insert_stmt = [insertSQL UTF8String];
sqlite3_prepare_v2(connectDB, insert_stmt, -1, &stm, NULL);
NSArray *paths =NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirec = [paths objectAtIndex:0];
NSString *imgePath = [documentsDirec stringByAppendingPathComponent:#"note.sqlite"];
if(sqlite3_open([imgePath UTF8String], &database) == SQLITE_OK){
const char *sql = "insert into images (images) values (?)";
if(sqlite3_prepare_v2(database, sql, -1, &addStmt, NULL) == SQLITE_OK){
UIImage *edtedImae = [info objectForKey:UIImagePickerControllerOriginalImage];
NSData *dataForImage = UIImagePNGRepresentation(edtedImae);
sqlite3_bind_blob(addStmt, 1, [dataForImage bytes], [dataForImage length], SQLITE_TRANSIENT);
NSLog(#"1st ..... %i",sqlite3_step(stm));
NSLog(#" 2nd.... %i",SQLITE_DONE);
if (sqlite3_step(stm) == SQLITE_DONE)
{
NSString* aName=name.text;
NSString* aSalary=salary.text;
NSString* aGid=gid.text;
UIImage* aPhoto=[UIImage imageWithData:imageData];
Person *person = [[Person alloc] initWithName:aName salary:aSalary gid:aGid photo:aPhoto];
[rootObject.list addObject:person];
[person release];
status.text = #"Contact added";
NSLog(#"contact added %# %#",name.text,gid.text);
NSLog(#"the count in person list is %i",[rootObject.list count]);
name.text = #"";
gid.text = #"";
salary.text = #"";
}
else
{
status.text = #"Failed to add contact";
}
sqlite3_finalize(stm);
sqlite3_close(connectDB);
}
}
Don't save the image in the database if you can avoid it. Instead, use columns to store the file name and any other attributes and then save the image as a document. I use a guid method to create file names. Don't forget to remove the document if you should remove the db entry.

Comparing NSString to SQLite database field

I have these codes that retrieve value from the SQLite database and comparing it to a NSString declared in the header file.
Retrieving from database:
NSString *sql = [[NSString alloc] initWithFormat:#"SELECT TESTONE, TESTTWO, TESTTHREE, TESTFOUR FROM STUDENTS WHERE NAME='%#'",Name];
sqlite3_stmt *statement;
if(sqlite3_prepare_v2(db, [sql UTF8String], -1, &statement, nil)== SQLITE_OK)
{
while(sqlite3_step(statement) == SQLITE_ROW){
char *one = (char *) sqlite3_column_text(statement,0);
tOne = [[NSString alloc] initWithUTF8String:one];
char *two = (char *) sqlite3_column_text(statement,1);
tTwo = [[NSString alloc] initWithUTF8String:two];
char *three = (char *) sqlite3_column_text(statement,2);
tThree = [[NSString alloc] initWithUTF8String:three];
char *four = (char *) sqlite3_column_text(statement,3);
tFour = [[NSString alloc] initWithUTF8String:four];
}
}
Comparing string:
if(tOne == #"Yes")
{
//blablabla
}
else if(tOne == #"No")
{
//blablabla
}
It doesn't seem to go into the 'IF' at all.
I have double-checked the field in the database and its TEXT datatype.
I made a UILabel to display the tOne value and it shows "Yes".
May i know what or where went wrong during the comparison?
You can't compare strings like this, you have to use isEqualToString:
if([tOne isEqualToString:#"Yes"])
{
//blablabla
}
else if([tOne isEqualToString:#"No"])
{
//blablabla
}
You are currently comparing the address of the NSString object, not its contents. Use the compare: or isEqualToString: family of methods to compare the content of the string.
Reference.