Thursday, September 12, 2013

Faster way of inserting bunch of records into SQLite database on the fly using Objective-C-iPhone/iPad

Hi Guys,i am back here with one more helful information regarding transaction with SQLite.

Before i start talking about faster way of inserting records into the database,lets see the basics first.

1) Copying your SQLite database from the bundle(in case you have it in your bundle)  to DOCUMENT/CACHE directory is important,before playing with it.


NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *my_DatabasePath = [documentsDirectory stringByAppendingPathComponent:@"URDatabase.sqlite"];


my_DatabasePath will get you the path of the database.Now lets check whether the database with my_DatabasePath is already present in your document directory,if not lets copy it.

The below method will check out whether your database is present in the directoryif not then it will copy from bundle and place it in the directory.


- (void) checkAndCreateDatabase:(NSString *)databasePath {
    NSError * error;

    //Check if the database has been saved to the users phone, if not then copy it over
    BOOL Success;

    //Create a file manager object, we will use this to check the status
    //of the databse and to copy it over if required
    NSFileManager *fileManager = [NSFileManager defaultManager];

    //Check if the database has already been created in the users filesystem
    Success = [fileManager fileExistsAtPath:databasePath];

    //If the database already exists then return without doing anything

    if(Success)
        return;

    //If not then proceed to copy the database from the application to the users filesystem

    //Get the path to the database in the application package
    NSString *databasePathFromApp = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:m_DatabaseName];

    //Copy the database from the package to the usrrs filesystem
    [fileManager copyItemAtPath:databasePathFromApp toPath:databasePath error:&error];

}

This is all about the basics.

2) Now lets move on to the topic what i mentioned as the title of article.

There exist many ways to insert data into your database,have anyone asked the following question with themselves?

(i) how much they are effective for the mobile devices like iPhone?
(ii) how long it will take to insert?
(iii) doest it block the main thead?

Ofcourse the answer is they are not effective,it will take so much time to insert the bunch of records into the sqlite database and YES,it does block the main thread until its done with the transaction.

So lets not worry about it,i have overcome from this issue with some R&D.And below is the result of it.

Have a look at the below code of inserting records into the sqlite database.

arrayOfRecords ==>Say i have huge records thats been stored in one global array.
myTable==> table name of my sqlite database.
databasePathVDB==> path of the database.
recordno,record1==>table field name.


-(void)insertAllRecordsIntoDB:(NSString *) databasePathVDB 
char* errorMessage
sqlite3_stmt* stmt = NULL; 
sqlite3 *mDb; 
if(sqlite3_open([databasePathVDB UTF8String], &mDb) == SQLITE_OK) 

sqlite3_exec(mDb, "BEGIN THE TRANSACTION", NULL, NULL, &errorMessage); 
char buffer[] = "INSERT/REPLACE INTO myTable(recordno, record1) Values(?,?)"; 

sqlite3_prepare_v2(mDb, buffer, strlen(buffer), &stmt, NULL); 
for (unsigned i = 0; iarrayOfRecords.count; i++) { 
//std::string id = getID(); 

sqlite3_bind_int(stmt, 0,i); 

sqlite3_bind_text(stmt, 1, [[arrayOfRecords objectAtIndex:i]UTF8String] ,-1, SQLITE_STATIC); 

if (sqlite3_step(stmt) != SQLITE_DONE) 
NSLog(@"Commit Failed!\n"); 
sqlite3_reset(stmt); 
sqlite3_exec(mDb, "COMMIT TRANSACTION", NULL, NULL, &errorMessage); 
sqlite3_finalize(stmt); 
sqlite3_close(mDb); 

}

That's it.The above code looks more sort of a normal C-code :-).If you still feel sheepy for using this method,then compare the other general methods with the above effective method.

Happy coding... :-)




Friday, March 22, 2013

Playing with CoreText Framework(iOS 3.0+)

First of all i would like you to know the simple definition of CoreText, its a text engine found in iOS 3.2+ and OSX 10.5+ that gives you fine-grained control over text layout and formatting.


It sits in the sweet spot between UIKit and Core Graphics/Quartz:
In UIKit you have UILabel and you add a word or a text line on the screen by simple Drag-and-Drop in IB, but you cannot change the color of individual words.
In Core Graphics/Quartz you can do pretty much everything the system is capable of, but you need to calculate the position of each glyph in your text and draw it on the screen.
Core Text lies right between the two! You have complete control over position, layout, attributes like color and size, but Core Text takes care of everything else for you – from word wrap to font rendering and more.
Now lets start playing with it.

Just assume that you want to draw/create a new lable/textview based on the input text(font size and font name),the first thing which will pop-up from your mind is "How can we create a lable/textview based on the input text?".

Here is the answer,before drawing the text into lable/textview,you need to draw it on temporary paper
CGSize theSize = [urtext sizeWithFont:[UIFont systemFontOfSize:17.0f] constrainedToSize:CGSizeMake(310.0f, FLT_MAX) lineBreakMode:UILineBreakModeWordWrap];
Supply the default width of the lable/textview to be created.Now with theSize.height will get you the textview/lable to be created.

Now importantly what we will do is,we will add one more subview at the end of text/paragraph in lable/textview.Sounds crazy?.I too felt the same before doing it ;)

Here we are,hope you have gone through Apples NSAttributed feature :)
NSAttributedString* text = TextView.attributedText;//you can use lable too
CTFramesetterRef fs =
CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)text);
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(0,0,self.aTextView.frame.size.width - 16,100000));  // why the -16? the frame should be the REAL TEXT FRAME, not the UITextView frame. If you look, there is a space between the view margin and the text. This is the baseline. Probably there is a method to calculate it programatically, but I can't check now. In my case it seems like 8px (*2)
CTFrameRef f = CTFramesetterCreateFrame(fs, CFRangeMake(0, 0), path, NULL);
CTFrameDraw(f, NULL);
NSRange lastRange;
NSArray* lines = (__bridge NSArray*)CTFrameGetLines(f);
id lastLineInArray = [lines lastObject];
CTLineRef theLine = (__bridge CTLineRef)lastLineInArray;
CFRange range = CTLineGetStringRange(theLine);
lastRange.length = range.length;
lastRange.location = range.location - 1;
NSLog(@"%ld %ld", range.location, range.length);
CGPathRelease(path);
CFRelease(f);
CFRelease(fs);
// this is the last line
NSString *lastLine = [aTextView.text substringWithRange:lastRange];
CGSize sizeOfString = [lastLine sizeWithFont:self.aTextView.font constrainedToSize:aTextView.frame.size;

you will obtain the width and the height of the string, and then the final position of your button (for the y position: number of lines taken from the lines array count * string height)
Now pass the correct rect to the UITextView/UILable and you're DONE!.

Happy Coding :-)...



Saturday, December 22, 2012

Feel the difference between HTML touch and Native touch on UIWebView

Posting something on sunday is never been easy ;).Ok let me start with next article,will brief you about HTML touch and native touch on UIWebView.So many iOS developers will find it difficult if they try to do their own,because most of the developers were not familiar in javascript.Hold on i am here to save your time :)

HTML Side:

Well i am not an expert at HTML/Javascript,but i will share the things which i know,and with which i achieved the success.I will take one simple html with one button. You're not going to distinguish the HTML and UIWebView touch by using onlick event in the html,so importantly you have to use the on "ontouchstart" event for the button tap,because when you tap on any button/image etc with onClick(); event,your UIWebView takes it both HTML and native touch and note that HTML touch will take the priority over native touch.

<button type="button" autofocus="autofocus" ontouchstart ="button1_click();">Click Me!</button>function button1_click()
{
htmltouch=1;
}


In the script will declare a global variable called "htmltouch",whenever i tap on any html events,i will make htmltouch=1.

<html>

<head>

<meta http-equiv="Content-type" content="text/html" charset="utf-8" />


<meta name="apple-mobile-web-app-capable" content="yes">


<meta name="viewport" content="width=768, height=1024, user-scalable=yes">

<script type="text/javascript">

 var htmlTouch = 0;

//function which we will call from oObjective-c
function toCallFromiOS() 
{

if( htmlTouch  == 1 ) 

alert("its a html touch");
return 'touched';
}    

}  

function resetTheHTMLtouch() 
{
htmlTouch = 0;
}     

function button1_click()
{
htmlTouch =1;
}

</script>

</head>

<body>

<button type="button" autofocus="autofocus" ontouchstart ="button1_click();">Click Me!</button>

</body>
<html>

//Wondering why i used toCallFromiOS and resetTheHTMLtouch function?.Those are the golden functions which will call from native.HTML side is completed,now bring it on!

Native Side:

Create UIWebview to load the above html (well i am doing it in local now).
self.webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
[self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] 
                                pathForResource:@"touch" ofType:@"html"] isDirectory:NO]]];

Now add the gesture delegate to the whole webview.
UITapGestureRecognizer *tapGestureDown = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture)];
tapGestureDown.numberOfTapsRequired = 1;
tapGestureDown.delegate = self;
[self.webView addGestureRecognizer:tapGestureDown];




//handleTapGesture is a native method,in the sense "On detecting if its a native touch,what you want perform?"
-(void)
handleTapGesture
{
NSLog(@"Touch is native");
}

Now we are all set.Next step is to add the delegate called  "-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer"

On detecting the touch event on the webview,the added delegate function gets called.

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
Now how do we proceed?.If you add the above code just like that,on tapping the webview the above delegate gets called N number of times(sometimes 8,9,13 etc).Only solution is we should be able to know the state of the touch(whether its end or start),to reset the touch even for the next call.

 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
NSString *javastr=[self.webView stringByEvaluatingJavaScriptFromString:@"toCallFromiOS();"];
NSLog(@"%@",javastr);

 if((otherGestureRecognizer.state==UIGestureRecognizerStateEnded && [javastr hasPrefix:@"touched"]))
{
       
javastr= [self.webView stringByEvaluatingJavaScriptFromString:@"resetTheHTMLtouch();"];
     
return NO;
}
    
if([javastr hasPrefix:@"touched"])
{
return NO;
}
    
return YES;

}

If the 
javastr returns any value its a HTML touch or else its a native touch,"handleTapGesture" gets called.


This is all about HTML touch and Native touch.Hope you guys enjoyed doing this.Good luck.:)

Thursday, December 20, 2012

PageCurl bug from iOS 5.1 to 6.0

After bleeding too much of blood to solve crashing issue while trying to flip the first page(left side) and last page(right side) , finally i have come up with following solution. :)

OLD CODE(iOS 5.1) : when returning nil on the first and last page you will experience the app crash.It works fine in iOS 5.1,but in iOS 6 it wont.


- (UIViewController *)pageViewController:
(UIPageViewController *)pageViewController viewControllerBeforeViewController:
(UIViewController *)viewController
{
    for (UIGestureRecognizer *recognizer in pageController.gestureRecognizers) {
        if ([recognizer isKindOfClass:[UITapGestureRecognizer class]]) {
            recognizer.enabled = NO;
        }
    }
    NSUInteger index = [self indexOfViewController:
                        (MainViewController *)viewController];
    if ((index == 0) || (index == NSNotFound)) {
        return nil;
    }
    
    index--;
    return [self viewControllerAtIndex:index];
}
- (UIViewController *)pageViewController:
(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
    for (UIGestureRecognizer *recognizer in pageController.gestureRecognizers) {
        if ([recognizer isKindOfClass:[UITapGestureRecognizer class]]) {
            recognizer.enabled = NO;
        }
    }
    NSUInteger index = [self indexOfViewController:
                        (MainViewController *)viewController];
    if (index == NSNotFound) {
        return nil;
    }
}

SOLUTION(iOS 6) : After adding the gesture effect to the superview,just call the delegate called -(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer. What i did is quiet simple, computing the speed of the user flipping the first page and last page (i mean using the gesture recognizer) , i denied the swiping.All you need to do is just paste the following code,and you are DONE!.

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
    if (pageNum==0) {
        
        if ([(UIPanGestureRecognizer*)gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] &&
            [(UIPanGestureRecognizer*)gestureRecognizer velocityInView:gestureRecognizer.view].x > 0.0f) {
            //NSLog(@"Swiping to left on 1st page is denied");
            return NO;
        }
        if ([(UITapGestureRecognizer*)gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]] &&
            [(UITapGestureRecognizer*)gestureRecognizer locationInView:gestureRecognizer.view].x < self.view.frame.size.width/2) {
         //NSLog(@"tapping to left on 1st page is denied");
            return NO;
        }
    }
    
    else if(pageNum ==totalNoOfFiles-1)
    {
        
        if ([(UIPanGestureRecognizer*)gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] &&
            [(UIPanGestureRecognizer*)gestureRecognizer velocityInView:gestureRecognizer.view].x < 0.0f) {
            //NSLog(@"Swiping to right on 1st page is denied");
            return NO;
        }
        if ([(UITapGestureRecognizer*)gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]] &&
            [(UITapGestureRecognizer*)gestureRecognizer locationInView:gestureRecognizer.view].x > self.view.frame.size.width/2) {
          //NSLog(@"Tapping to right on 1st page is denied");
            return NO;
        }
    }
    
    return YES;
}


- (UIViewController *)pageViewController:(UIPageViewController*) pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
 {
  int index = [self indexOfViewController:(ChildViewController *)viewController];
                index--;
        
    return [self viewControllerAtIndex:index];
}


- (UIViewController *)pageViewController:
(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
    int index = [self indexOfViewController:(ChildViewController *)viewController];
          
            index++;
     return [self viewControllerAtIndex:index];
}



Hope your life is saved :)