August 14, 2008

Calling Objective-C from JavaScript in an iPhone UIWebView

Posted in iPhone development tagged , , , , , , , , , , , at 6:47 am by tetontech

Apple now has a new web view class, WKWebView. It is much improved. See this new posting for how to interact with Objective-C from JavaScript and this post for interacting with Swift. The classes and methods are the same for both Swift and Objective-C. (June 13, 2014).

 

The information and source code covered in this posting is included, updated, and made more reliable in the downloadable framework called QuickConnectiPhone. Take a look at this wiki page to learn more more about the built in capabilities and features of this framework. These include GPS, acceleration, and much, much more functionality available from within JavaScript.

The framework uses the approach described below in the original posting but has many features, optimizations, and defect fixes wrapped up for you and ready to use.

 

At long last a methodology has been discovered that will allow calls to be made to objective-c code from the UIWebView. It isn’t as strait forward as I would like but it does work. The example shown here uses it to activate the core location libraries as well as to send messages to NSLog. We all know how hard it can be to find the reasons for JavaScript failures in the UIWebview. This will allow you to do it.

The methodolog uses the UIWebViewDelegate method

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType

The post contains an updated version of code from a previous blog post here at Teton Technical. It includes changes that will soon be in the next version of the QuickConnectiPhone framework.

In order to use this methodology a button with an onclick listener has been defined in the index.html file as seen here.

It tells the UIWebView to load a new page called ‘call’ and passes two parameters, a command and a parameter that can be used by the objective-c. In this case it is actually not used but is here to show you how parameters are passed.

Here is the code fragment that contains the objective-c method that is triggered when a location change is requested,the full code for the class containing this method is at the end of this posting. It checks to see if ‘index.html’ is being called.
If it is then it returns YES so that the load will continue. Any other request for a page changes is stopped by returning NO.
It also parses the URL that is the request and retrieves the cmd and any other parameters sent in the URL.
If you were using the QuickConnectOC, Objective-C, library in on the objective-c side of your application you could simply pass the command and parameter array to it and let it handle it for you.
If you are not using the QuickConnectOC library you will need to handle it yourself with conditional statements. Since I have not yet posted the QCOC library, it should be available this week, I have shown how to embed the conditionals statements for doing logging of messages and calling core location functionallity.

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
NSString *url = [[request URL] absoluteString];

NSArray *urlArray = [url componentsSeparatedByString:@"?"];
NSString *cmd = @"";
NSMutableArray *paramsToPass = nil;
if([urlArray count] > 1){
NSString *paramsString = [urlArray objectAtIndex:1];
NSArray *urlParamsArray = [paramsString componentsSeparatedByString:@"&"];
cmd = [[[urlParamsArray objectAtIndex:0] componentsSeparatedByString:@"="] objectAtIndex:1];
int numCommands = [urlParamsArray count];
paramsToPass = [[NSMutableArray alloc] initWithCapacity:numCommands-1];
for(int i = 1; i < numCommands; i++){
NSString *aParam = [[[urlParamsArray objectAtIndex:i] componentsSeparatedByString:@"="] objectAtIndex:1];

 

[paramsToPass addObject:aParam];
}
}
/*
* if you are using QuickConnectOC within the Objective-C portion of your iPhone application
* you can make the call that is commented out below.
*/
//[[QuickConnect getInstance] handleRequest:cmd withParameters:paramsToPass];
/*
* if you are not using QuickConnectOC you will need to use conditional statements like if-then-else or case statements
* like the example below. You would then use the other parameters passed with the command to make decistions and
* execute code.
*/
if([cmd compare:@"getLocation"] == NSOrderedSame){
[locationManager startUpdatingLocation];
}
else if([cmd compare:@"logMessage"] == NSOrderedSame){
//NSString *message = [[paramsToPass objectAtIndex:0] stringByReplacingOccurrencesOfString:@"%20" withString:@" "];
NSString *message = [[paramsToPass objectAtIndex:0] stringByReplacingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
NSLog(@"JSMessage: %@",message);
}
/*
* Only load the page if it is the initial index.html file
*/
NSRange aSubStringRange = [url rangeOfString:@"index.html"];
if(aSubStringRange.length != 0){
return YES;
}
else{
return NO;
}
}

The JavaScript function, GPSLocation, in index.html that gets called by objective-c is shown below. It shows you how to send messages to be logged by NSLog. This technique should allow you to put any number of debug statements in your JavaScript code and have them displayed in Xcodes run log. This code can be found in the QCUtilities.js file in the soon to be released QCiPhone beta 5 framework.
// the location function call made from the underlying Objective-C framework when the location of the device is determined
function GPSLocation(longitude, latitude, altitude){
window.location = "call?cmd=logMessage&msg="+document.getElementById('messages').innerText;
try{

 

document.getElementById('messages').innerText = 'Longitude: '+longitude+' \nLatitude: '+latitude+'\nAltitude: '+altitude;
return 'It worked!';
}
catch(err)
{
txt="'There was an error on this page.\n\n";
txt+="Error description: " + err.description + "'";
window.location = "call?cmd=logMessage&msg="+txt;
}
return 'It failed';
}

There is no guarantee that these messages will be logged prior to the function shown here returning. In fact it has not happened for me yet. Here is the log file results. You can see that the JavaScript log message appears after the return value of the GPSLocation function has been logged by the method that called it.

2008-08-14 00:05:46.638 RoadRunner2[38029:20b] Location updated
2008-08-14 00:05:46.639 RoadRunner2[38029:20b] GPSLocation(37.331689, -122.030731, 0.000000);
2008-08-14 00:05:46.642 RoadRunner2[38029:20b] done with result: ‘It worked!’
2008-08-14 00:05:46.647 RoadRunner2[38029:20b] JSMessage: Put Messages Here

I hope this helps you all out.

Here is the full code for the updated UIWebView example.
/*
Copyright 2008 Lee S. Barney

This file is part of QuickConnectiPhoneHybrid.

QuickConnectAJAX is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

QuickConnectAJAX is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with QuickConnectAJAX. If not, see .

*/
//the acceleration function call that is made from the underlying Objective-C framework when the device experiences acceleration
function accelerate(x, y, z){
/*
document.getElementById(‘messages’).innerHTML = ‘got acceleration’;
document.getElementById(‘messages’).innerHTML += ‘
session value: ‘+session.getAttribute(‘curAccel’);
*/
var accelObject = session.getAttribute(‘curAccel’);
//document.getElementById(‘message’).innerHTML += ‘
xValue: ‘+accelObject.x+’ new x: ‘+x;
accelObject.x = x;
accelObject.y = y;
accelObject.z = z;

handleRequest(‘accel’);

return true;
}
//an object to hold acceleration values in all three dimensions.
function AccelerationObject(){
this.x = 0;
this.y = 0;
this.z = 0;
}

// the location function call made from the underlying Objective-C framework when the location of the device is determined
function GPSLocation(longitude, latitude, altitude){
window.location = “call?cmd=logMessage&msg=”+document.getElementById(‘messages’).innerText;
try{

document.getElementById(‘messages’).innerText = ‘Longitude: ‘+longitude+’ \nLatitude: ‘+latitude+’\nAltitude: ‘+altitude;
return ‘It worked!’;
}
catch(err)
{
txt=”‘There was an error on this page.\n\n”;
txt+=”Error description: ” + err.description + “‘”;
window.location = “call?cmd=logMessage&msg=”+txt;
}
return ‘It failed’;
}
//this function will scroll the current view by the x and y amount. This function is ususally called by the Objective-C portion of an application.
function customScroll(xAmount, yAmount){
//document.getElementByID(‘message’).innerHTML = xAmount+” “+yAmount+”
“;
window.scrollBy(xAmount,yAmount);
return ‘done’;
}

//remove white space from the beginning and end of a string
function trim(aString){
return aString.replace(/^\s+|\s+$/g, ”);
}

//replace all occurances of a string with another
function replaceAll(aString, replacedString, newSubString){
while(aString.indexOf(replacedString) > -1){
aString = aString.replace(replacedString, newSubString);
}
return aString;
}
//stop an event from doing its’ default behavior since it will be handled in the BCO and VCO
function stopDefault(event){
if(event){
if (event.preventDefault)// non-IE
event.preventDefault();
event.returnValue = false;// IE
}
}
/*
* These mapping functions require functions for the business rules,
* view controls, error controls, and security controls NOT the names
* of these items as strings.
*/
function mapCommandToBCF(aCmd, aBRule){
var aMappingBean = new Array();
aMappingBean['bcf'] = aBRule;
businessMap[aCmd] = aMappingBean;
}
function mapCommandToVCF(aCmd, aVCF){
var aMappingBean = viewMap[aCmd];
if(aMappingBean == null){
aMappingBean = new Array();
aMappingBean['viewFunctionArray'] = new Array();
viewMap[aCmd] = aMappingBean;
}
var funcArray = aMappingBean['viewFunctionArray'];
funcArray.push(aVCF);
}
function mapCommandToECF(anErrorCmd, anECF){
var aMappingBean = new Array();
aMappingBean['ecf'] = anECF;
errorMap[anErrorCmd] = aMappingBean;
}
function mapCommandSCF(aCmd, aSCF){
var aMappingBean = securityMap[aCmd];
if(aMappingBean == null){
aMappingBean = new Array();
aMappingBean['securityFunctionArray'] = new Array();
securityMap[aCmd] = aMappingBean;
}
var funcArray = aMappingBean['securityFunctionArray'];
funcArray.push(aSCF);
}

function mapCommandToValCF(aCmd, aValCF){
var aMappingBean = validationMap[aCmd];
if(aMappingBean == null){
aMappingBean = new Array();
aMappingBean['validationFunctionArray'] = new Array();
validationMap[aCmd] = aMappingBean;
}
aMappingBean['validationFunctionArray'].push(aValCF);
}

 

function debug(msg){
document.getElementById('debug').innerHTML = msg;
}

August 12, 2008

Executing Multiple Statements in a SQLlite transaction

Posted in iPhone development tagged , , , , , , , at 4:00 pm by tetontech

Here is a quick example of how to execute multiple statements serially within a SQLite transaction.  This example comes from the next beta of QuickConnectiPhone.  I’m releasing it early here.

 

This code clears the values out of a table by dropping the table and then re-creating it all within one transaction and one asynchronous call.

function clearValues(){

    var dropSQL = 'DROP TABLE session';

    var createSQL = "CREATE TABLE IF NOT EXISTS session (key TEXT UNIQUE, information TEXT)";

    

    this.db.transaction(function(tx) {

            tx.executeSql(dropSQL);//end of main executeSql call

            tx.executeSql(createSQL);

        });//end of transaction callback function

}

Follow

Get every new post delivered to your Inbox.

Join 318 other followers

%d bloggers like this: