Executing Javascript from Objective C in Cordova Plugin - javascript

I'm using cordova 6.5.0 and I'm creating a plugin from which I would like to execute some javascript.
I have found in forums that from my webview I can use stringByEvaluatingJavascriptFromString but it can not be recognised as a valid method.
For instance in AppDelegate.m, just for testing, I've tried the following:
[self.viewController.webView stringByEvaluatingJavascriptFromString]:#"alert('Test')"];
but I receive the message "No visible #inteface for 'UIView' declares the selector 'stringByEvaluatingJavascriptFromString'".
I have created a class object named Utils.m
#implementation Utils: NSObject
id webview;
id delegate;
-(void) initialise:(id) wbview Delegate:(id) dlg{
webview = wbview;
delegate = dlg;
}
-(void) executeJavascript:(NSString *)str{
[delegate runInBackground:^{
[delegate stringByEvaluatingJavaScriptFromString:#"alert('test')"];
}];
}
And then from the cordova plugin from pluginInitialize I have
- (void)pluginInitialize{
/* Start pjsua app thread */
// [NSThread detachNewThreadSelector:#selector(pjsuaStart) toTarget:self withObject:nil];
utils = [[Utils alloc] init ];
[utils initialise:self.webView Delegate:self.commandDelegate];
[utils executeJavascript:#"alert('Test');"];
}
Although for some reason the stringByEvaluatingJavascriptFromString is considered a valid method, the application crashes...
Any suggestions?
Thanks
Symeon

Use evalJs not stringByEvaluatingJavaScriptFromString:
[delegate evalJs:#"alert('test')"];

Related

Send event to js from swift or objective-c

I have created the following class (condensed version), heres a reference to the full file
https://github.com/cotyembry/CastRemoteNative/blob/7e74dbc56f037cc61241f6ece24a94d8c52abb32/root/ios/CastRemoteNative/NativeMethods.swift
#objc(NativeMethods)
class NativeMethods: RCTEventEmitter {
#objc(sendEventToJSFromJS)
func sendEventToJSFromJS {
self.emitEvent(eventName: "test", body: "bodyTestString")
}
func emitEvent(eventName: String: body: Any) {
self.sendEvent(withName: eventName, body: body)
}
}
This works perfectly and fires my callback listener that is in my javascript code when I call the emitEvent method like the following, its an altered snippet from
https://github.com/cotyembry/CastRemoteNative/blob/7e74dbc56f037cc61241f6ece24a94d8c52abb32/root/js/Components/ChromecastDevicesModal.js
From the javascript side
import {
NativeModules,
NativeEventEmitter
} from 'react-native'
//here I bring in the swift class to use inside javascript
var NativeMethods = NativeModules.NativeMethods;
//create an event emitter to use to listen for the native events when they occur
this.eventEmitter = new NativeEventEmitter(NativeMethods);
//listen for the event once it sends
this.subscription = this.eventEmitter.addListener('test', (body) => { console.log('in test event listener callback', body)});
NativeMethods.sendEventToJSFromJS() //call the native method written in swift
I simply have the sendEventToJSFromJS method invoked on a button press in javascript
Again, this works and the console.log('in test event listener callback', body) code works and runs on the javascript side
My Issue where this does NOT work:
If I was to do the following inside the swift file after defining the class, this would not work:
var nativeMethodsInstance = nativeMethods()
nativeMethodsInstance.sendEventToJSFromSwift()
Why? Because the following error is thrown:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'bridge is not set. This is probably because you've explicitly synthesized the bridge in NativeMethods, even though it's inherited from RCTEventEmitter.'
So, when creating an instance of NativeMethods, versus not... what is the difference?
For additional information:
Objective-C gets the same bridge not set issue when I write these same snippets of code in .h and .m files instead of in .swift files
I found where the error message is getting printed in the native code, but it just has the variable
_bridge
and is checking to see if it is nil
The files are this error comes from is:
RCTEventEmitter.h
RCTEventEmitter.c
here is the full snippet of RCTEventEmitter.c
- (void)sendEventWithName:(NSString *)eventName body:(id)body
{
RCTAssert(_bridge != nil, #"bridge is not set. This is probably because you've "
"explicitly synthesized the bridge in %#, even though it's inherited "
"from RCTEventEmitter.", [self class]);
if (RCT_DEBUG && ![[self supportedEvents] containsObject:eventName]) {
RCTLogError(#"`%#` is not a supported event type for %#. Supported events are: `%#`",
eventName, [self class], [[self supportedEvents] componentsJoinedByString:#"`, `"]);
}
if (_listenerCount > 0) {
[_bridge enqueueJSCall:#"RCTDeviceEventEmitter"
method:#"emit"
args:body ? #[eventName, body] : #[eventName]
completion:NULL];
} else {
RCTLogWarn(#"Sending `%#` with no listeners registered.", eventName);
}
}
Where does this _bridge value get set and how does it get set so I can know, in the cases where it is failing how to set it
I found the following also in RCTEventEmitter.h
#property (nonatomic, weak) RCTBridge *bridge;
In the error that is given it mentions the bridge is inherited in the RCTEventEmitter, so is this maybe an issue with the weak part to the bridge property?
Or do I need to change my strategy in how I'm doing this all together?
I know it probably has to be something to do with me not fully understanding the
#synthesize bridge = _bridge;
part of the code and all the languages being mixed in doesnt help much lol...
This is really hard, so any help would be much appreciated!
Thanks so much for your time
here is a link to the full project when the project history code represented the code from my question above (since I have since made changes to the project):
https://github.com/cotyembry/CastRemoteNative/tree/7e74dbc56f037cc61241f6ece24a94d8c52abb32
I figured it out
Warning: this solution uses a deprecated method react native method - I could not figure out how to "properly" inherit from the RCTEventEmitter and send an event... every time I tried to the _bridge would end up being nil
Make sure Swift is bridged to Objective C (if you're using swift to send the event to javascript)
Do Not create instances of the exported Native modules (whether they be written in Swift or Objective C)
Let React Native's underlying implementation do this and for each and every class that needs to send an event, export that particular Native Class Objective C Implementation code or Swift code (the Native Module) to React-Native. This allows the javascript to be able to listen to the event
var publicBridgeHelperInstance = PublicBridgeHelper() //instantiate the the objective c class from inside the .swift file to use later when needing to get a reference to the bridge to send an event to javascript written in react native
#objc(DeviceManager) //export swift module to objective c
class DeviceManager: NSObject {
#objc(deviceDidComeOnline:) //expose the function to objective c
public func deviceDidComeOnline(_ device: GCKDevice) {
//imagine this deviceDidComeOnline function gets called from something from the Native code (totally independent of javascript) - honestly this could be called from a native button click as well just to test it works...
//emit an event to a javascript function that is a in react native Component listening for the event like so:
//1. get a reference to the bridge to send an event through from Native to Javascript in React Native (here is where my custom code comes in to get this to actually work)
let rnBridge = publicBridgeHelperInstance.getBridge() //this gets the bridge that is stored in the AppDelegate.m file that was set from the `rootView.bridge` variable (more on this later)
//(if you want to print the bridge here to make sure it is not `nil` go ahead:
print("rnBridge = \(rnBridge)")
//2. actually send the event through the eventDispatcher
rnBridge?.eventDispatcher().sendAppEvent(withName: "test", body: "testBody data!!!")
}
}
in AppDelegate.h put (additionally to the code that was already in the file)
#import "YourProjectsBridgingHeaderToMakeThisCodeAvailableInSwift.h" //replace this with your actual header you created when creating a swift file (google it if you dont know how to bridge swift to objective c)
#interface PublicBridgeHelper: NSObject
-(RCTBridge*)getBridge;
#end
in AppDelegate.m put (in addition to the code that was already in the file)
#import <React/RCTRootView.h>
RCTBridge *rnBridgeFromRootView;
#implementation PublicBridgeHelper //this is created to SIMPLY return rnBridgeFromRootView defined above over to my Swift class when actually sending the event to javascript that defines a react native Component
-(RCTBridge*)getBridge {
NSLog(#"rnBridgeFromRootView = #%#", rnBridgeFromRootView);
return rnBridgeFromRootView;
}
important - also make sure to add the following line of code to the Objective C .h's bridging header to make this PublicBridgeHelper definition available to be used in the .swift code
#import "AppDelegate.h"
finally,
now to show you how to set the rnBridgeFromRootView variable used in AppDelegate.m (that gets returned and used in the .swift code right before sending the event to javascript)
open AppDelegate.m and in the method body of
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ... }
include the following after the line of code that instantiates the rootView variable
i.e. after the line that probably looks like
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName:#"YourProjecNameProbably" initialProperties:nil launchOptions:launchOptions];
add:
rnBridgeFromRootView = rootView.bridge //set the bridge to be exposed and returned later and used by the swift class
Now to explain the publicBridgeHelperInstance.getBridge() part that is in the .swift file
publicBridgeHelper is an instance of an objective c class which allows the swift class ability to get a reference to the react native bridge
If you are still having problems understanding my answer after reading this I made a video over it and you can watch it here:
https://www.youtube.com/watch?v=GZj-Vm9cQIg&t=9s

how to solve window.close() not work in Android webview c#

i just new using this visual studio c# android...
have system develop in vb.net... working fine in website... problem when wanted using mobile app... used this vs c# xamarin android....
protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);
WebView localWebView = FindViewById<WebView>(Resource.Id.LocalWebView);
localWebView.SetWebViewClient(new WebViewClient()); // stops request going to Web Browser
localWebView.Settings.JavaScriptEnabled = true;
localWebView.Settings.JavaScriptCanOpenWindowsAutomatically = true;
localWebView.SetWebChromeClient(new WebChromeClient());
localWebView.LoadUrl("http://www.facebook.com");
}
success run on this webview.... but have form have window.open....problem is how to window.close after used javascript to opened it.. means going back to my previous window.. and pass some information.... like: window.opener.document.getElementById(StrCtrlName2).value = MemberCode;
find and want to try this code... but look different for c#... how to convert it in visual studio c# because have error??... help me.. where to pun also i'm not sure... just learn c#
WebChromeClient webClient = new WebChromeClient(){
public void onCloseWindow(Window w){
super.onCloseWindow(w);
Log.d(TAG, "Window close");
}
};
thanks...
Welcome #haris!
The code you found is java. It's different from c# but also have a lot similarities.
If you want this particular java snippet in c# you would have do write something like this:
using Android.Webkit;
public class CustomWebChromeClient : WebChromeClient {
public override void OnCloseWindow(Android.Webkit.WebView window)
{
base.OnCloseWindow(window);
//Your favorite logging library call.
}
}
A quick explanation (in case you are interested).
In java snippet a ref to anonymous class which extends WebChromeClient is created and then we extend base onCloseWindow method by adding addition logging.
In c# the same can not be done so what I did, just created a named class CustomWebChromeClient which extends WebChromeClient and overrides OnCloseWindow
For more info refer to official Xamarin docs.

iOS JavaScript bridge

I'm working on an app where I'm going to use both HTML5 in UIWebView and native iOS framework together. I know that I can implement communication between JavaScript and Objective-C. Are there any libraries that simplify implementing this communication? I know that there are several libraries to create native iOS apps in HTML5 and javascript (for example AppMobi, PhoneGap), but I'm not sure if there is a library to help create native iOS apps with heavy JavaScript usage. I need to:
Execute JS methods from Objective-C
Execute Objective-C methods from JS
Listen to native JS events from Objective-C (for example DOM ready event)
There are a few libraries, but I didn't used any of these in big projects, so you might want to try them out:
WebViewJavascriptBridge: https://github.com/marcuswestin/WebViewJavascriptBridge
GAJavaScript: https://github.com/newyankeecodeshop/GAJavaScript
—
However, I think it's something simple enough that you might give it a try yourself. I personally did exactly this when I needed to do that. You might also create a simple library that suits your needs.
1. Execute JS methods from Objective-C
This is really just one line of code.
NSString *returnvalue = [webView stringByEvaluatingJavaScriptFromString:#"your javascript code string here"];
More details on the official UIWebView Documentation.
2. Execute Objective-C methods from JS
This is unfortunately slightly more complex, because there isn't the same windowScriptObject property (and class) that exists on Mac OSX allowing complete communication between the two.
However, you can easily call from javascript custom-made URLs, like:
window.location = yourscheme://callfunction/parameter1/parameter2?parameter3=value
And intercept it from Objective-C with this:
- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType {
NSURL *URL = [request URL];
if ([[URL scheme] isEqualToString:#"yourscheme"]) {
// parse the rest of the URL object and execute functions
}
}
This is not as clean as it should be (or by using windowScriptObject) but it works.
3. Listen to native JS events from Objective-C (for example DOM ready event)
From the above explanation, you see that if you want to do that, you have to create some JavaScript code, attach it to the event you want to monitor and call the correct window.location call to be then intercepted.
Again, not clean as it should be, but it works.
The suggested method of calling objective c from JS in the accepted answer isn't recommended. One example of problems: if you make two immediate consecutive calls one is ignored (you can't change location too quickly).
I recommend the following alternative approach:
function execute(url)
{
var iframe = document.createElement("IFRAME");
iframe.setAttribute("src", url);
document.documentElement.appendChild(iframe);
iframe.parentNode.removeChild(iframe);
iframe = null;
}
You call the execute function repeatedly and since each call executes in its own iframe, they should not be ignored when called quickly.
Credits to this guy.
Update: This has changed in iOS 8. My answer applies to previous versions.
An alternative, that may get you rejected from the app store, is to use WebScriptObject.
These APIs are public on OSX but are not on iOS.
You need to define interfaces to the internal classes.
#interface WebScriptObject: NSObject
#end
#interface WebView
- (WebScriptObject *)windowScriptObject;
#end
#interface UIWebDocumentView: UIView
- (WebView *)webView;
#end
You need to define your object that's going to serve as your WebScriptObject
#interface WebScriptBridge: NSObject
- (void)someEvent: (uint64_t)foo :(NSString *)bar;
- (void)testfoo;
+ (BOOL)isKeyExcludedFromWebScript:(const char *)name;
+ (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector;
+ (WebScriptBridge*)getWebScriptBridge;
#end
static WebScriptBridge *gWebScriptBridge = nil;
#implementation WebScriptBridge
- (void)someEvent: (uint64_t)foo :(NSString *)bar
{
NSLog(bar);
}
-(void)testfoo {
NSLog(#"testfoo!");
}
+ (BOOL)isKeyExcludedFromWebScript:(const char *)name;
{
return NO;
}
+ (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector;
{
return NO;
}
+ (NSString *)webScriptNameForSelector:(SEL)sel
{
// Naming rules can be found at: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/WebKit/Protocols/WebScripting_Protocol/Reference/Reference.html
if (sel == #selector(testfoo)) return #"testfoo";
if (sel == #selector(someEvent::)) return #"someEvent";
return nil;
}
+ (WebScriptBridge*)getWebScriptBridge {
if (gWebScriptBridge == nil)
gWebScriptBridge = [WebScriptBridge new];
return gWebScriptBridge;
}
#end
Now set that an instance to your UIWebView
if ([uiWebView.subviews count] > 0) {
UIView *scrollView = uiWebView.subviews[0];
for (UIView *childView in scrollView.subviews) {
if ([childView isKindOfClass:[UIWebDocumentView class]]) {
UIWebDocumentView *documentView = (UIWebDocumentView *)childView;
WebScriptObject *wso = documentView.webView.windowScriptObject;
[wso setValue:[WebScriptBridge getWebScriptBridge] forKey:#"yourBridge"];
}
}
}
Now inside of your javascript you can call:
yourBridge.someEvent(100, "hello");
yourBridge.testfoo();
In iOS8 you can look at WKWebView instead of UIWebView. This has the following class:
WKScriptMessageHandler: Provides a method for receiving messages from JavaScript running in a webpage.
This is possible with iOS7, checkout http://blog.bignerdranch.com/3784-javascriptcore-and-ios-7/
Your best bet is Appcelerators Titanium offering. They already have built a Obj-C javascript bridge using the V8 engine JavascriptCore engine used by webkit. It's also open source, so you'll be able to download it and tinker with the Obj-C as you like.
Have a look at the KirinJS project: Kirin JS which allows to use Javascript for the application logic and native UI adequate to the platform it runs on.
I created a library like WebViewJavascriptBridge, but it's more JQuery-like, has easier to setup and is easier to use. Doesn't rely on jQuery (though to its credit, had I known WebViewJavascriptBridge existed before writing this I may just have held back slightly before diving in). Let me know what you think! jockeyjs
If you are using WKWebView on iOS 8, take a look the XWebView which can automatically expose the native interface to javascript.

Possible to call an Objective-C method from Javascript in a Webview? [duplicate]

I'm developing a native iPhone app using Phonegap, so everything is done in HTML and JS. I am using the Flurry SDK for analytics and want to use the
[FlurryAPI logEvent:#"EVENT_NAME"];
method to track events. Is there a way to do this in Javascript? So when tracking a link I would imagine using something like
<a onClick="flurryTrackEvent("Click_Rainbows")" href="#Rainbows">Rainbows</a>
<a onClick="flurryTrackEvent("Click_Unicorns")" href="#Unicorns">Unicorns</a>
"FlurryAPI.h" has the following:
#interface FlurryAPI : NSObject {
}
+ (void)startSession:(NSString *)apiKey;
+ (void)logEvent:(NSString *)eventName;
+ (void)logEvent:(NSString *)eventName withParameters:(NSDictionary *)parameters;
+ (void)logError:(NSString *)errorID message:(NSString *)message exception:(NSException *)exception;
+ (void)setUserID:(NSString *)userID;
+ (void)setEventLoggingEnabled:(BOOL)value;
+ (void)setServerURL:(NSString *)url;
+ (void)setSessionReportsOnCloseEnabled:(BOOL)sendSessionReportsOnClose;
#end
I'm only interested in the logEvent method(s). If it's not clear by now, I'm comfortable with JS but a recovering Obj-C noob. I've read the Apple docs but the examples described there are all for newly declared methods and I imagine this could be simpler to implement because the Obj-C method(s) are already defined.
Thank you in advance for any input.
One way to do this is to setup a delegate on the UIWebView which has the shouldStartLoadEvent. Inside that event, you check what URL the UIWebView is trying to navigate to. Now to communicate from JavaScript to Objective-C, you need to specify your own custom anchors which will trigger different actions. For example, to log something, you might decide to use the anchor "#FAPI_LogEvent_Click_Rainbows".
In JavaScript, you could have methods defined like such:
function flurryTrackEvent(text) {
window.location.href = 'FAPI_LogEvent' + text;
}
function flurrySetUserID(userID) {
window.location.href = 'FAPI_SetUserID' + userID;
}
Next, in Objective-C, you would implement the shouldStartLoadEvent and "capture" these href navigations, and tell the browser not to load them. You will need to split the string up yourself and call the appropriate function. Here's some code:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType () {
NSString *theAnchor = [[request URL] fragment];
if ([theAnchor hasPrefix:#"FAPI_LogEvent"]) {
NSString *textToLog = [theAnchor substringFromIndex:[#"FAPI_LogEvent" length]];
[FlurryAPI logEvent:textToLog];
return NO; // prevent the UIWebView from navigating to this anchor
} else if ([theAnchor hasPrefix:#"FAPI_SetUserID"]) {
NSString *userID = [theAnchor substringFromIndex:[#"FAPI_SetUserID" length]];
[FlurryAPI setUserID:userID];
return NO; // prevent the UIWebView from navigating to this anchor
}
}
The fact that the events are already defined in Objective-C doesn't really help much since you need to implement your own routing behavior to call the appropriate Objective-C method. The only way you could take advantage of the fact that the methods are already defined in Objective-C and avoid hard coding the routing logic, would be using #selectors or similar dynamic function calling which is available in Objective-C. However, this is much more complicated to implement and probably presents a security risk. I would recommend implementing the routing logic like is shown in the code above.
PhoneGap has functionality for adding native plugins, to add a Flurry log event plugin for iOS I would do something like this:
Add a PGFlurry PhoneGap plugin class:
PGFlurry.h
#import <PhoneGap/PGPlugin.h>
#interface PGFlurry : PGPlugin
- (void)logEvent:(NSArray*)arguments withDict:(NSDictionary*)options;
#end
PGFlurry.m
#import "PGFlurry.h"
#import "FlurryAPI.h"
#implementation PGFlurry
// if you init Flurry somewhere else you can remove this method
- (PGPlugin*) initWithWebView:(UIWebView*)theWebView {
self = [super init];
if (self == nil) {
return nil;
}
// init and start Flurry
[FlurryAPI startSession:#"API key"];
return self;
}
- (void)logEvent:(NSArray*)arguments withDict:(NSDictionary*)options {
[FlurryAPI logEvent:[arguments objectAtIndex:0]];
}
#end
Add a JavaScript plugin helper to the www folder:
Flurry.js
PhoneGap.addConstructor(function() {
if(!window.plugins) {
window.plugins = {};
}
window.plugins.flurry = {
logEvent: function(name) {
return PhoneGap.exec("PGFlurry.logEvent", name);
}
}
});
Add the plugin to PhoneGap.plist by adding a key/value pair with both the key and value being "PGFlurry" to the "plugins" dictionary.
Now you should be able to use it like this:
<!DOCTYPE html>
<html>
<head>
<script src="phonegap.js" type="text/javascript"></script>
<script src="flurry.js" type="text/javascript"></script>
<script type="text/javascript">
document.addEventListener("deviceready", function() {
window.plugins.flurry.logEvent("Testing");
}, false);
</script>
</head>
<body>
</body>
</html>
You can find the Phonegap Flurry Plugin written by me at
https://github.com/designerkamal/Phonegap-Flurry-Plugin
Don't use their objective-c library, use their js library and you won't have to worry about objective-c. :)

How do I call an Objective-C method from Javascript in UIWebView?

I'm developing a native iPhone app using Phonegap, so everything is done in HTML and JS. I am using the Flurry SDK for analytics and want to use the
[FlurryAPI logEvent:#"EVENT_NAME"];
method to track events. Is there a way to do this in Javascript? So when tracking a link I would imagine using something like
<a onClick="flurryTrackEvent("Click_Rainbows")" href="#Rainbows">Rainbows</a>
<a onClick="flurryTrackEvent("Click_Unicorns")" href="#Unicorns">Unicorns</a>
"FlurryAPI.h" has the following:
#interface FlurryAPI : NSObject {
}
+ (void)startSession:(NSString *)apiKey;
+ (void)logEvent:(NSString *)eventName;
+ (void)logEvent:(NSString *)eventName withParameters:(NSDictionary *)parameters;
+ (void)logError:(NSString *)errorID message:(NSString *)message exception:(NSException *)exception;
+ (void)setUserID:(NSString *)userID;
+ (void)setEventLoggingEnabled:(BOOL)value;
+ (void)setServerURL:(NSString *)url;
+ (void)setSessionReportsOnCloseEnabled:(BOOL)sendSessionReportsOnClose;
#end
I'm only interested in the logEvent method(s). If it's not clear by now, I'm comfortable with JS but a recovering Obj-C noob. I've read the Apple docs but the examples described there are all for newly declared methods and I imagine this could be simpler to implement because the Obj-C method(s) are already defined.
Thank you in advance for any input.
One way to do this is to setup a delegate on the UIWebView which has the shouldStartLoadEvent. Inside that event, you check what URL the UIWebView is trying to navigate to. Now to communicate from JavaScript to Objective-C, you need to specify your own custom anchors which will trigger different actions. For example, to log something, you might decide to use the anchor "#FAPI_LogEvent_Click_Rainbows".
In JavaScript, you could have methods defined like such:
function flurryTrackEvent(text) {
window.location.href = 'FAPI_LogEvent' + text;
}
function flurrySetUserID(userID) {
window.location.href = 'FAPI_SetUserID' + userID;
}
Next, in Objective-C, you would implement the shouldStartLoadEvent and "capture" these href navigations, and tell the browser not to load them. You will need to split the string up yourself and call the appropriate function. Here's some code:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType () {
NSString *theAnchor = [[request URL] fragment];
if ([theAnchor hasPrefix:#"FAPI_LogEvent"]) {
NSString *textToLog = [theAnchor substringFromIndex:[#"FAPI_LogEvent" length]];
[FlurryAPI logEvent:textToLog];
return NO; // prevent the UIWebView from navigating to this anchor
} else if ([theAnchor hasPrefix:#"FAPI_SetUserID"]) {
NSString *userID = [theAnchor substringFromIndex:[#"FAPI_SetUserID" length]];
[FlurryAPI setUserID:userID];
return NO; // prevent the UIWebView from navigating to this anchor
}
}
The fact that the events are already defined in Objective-C doesn't really help much since you need to implement your own routing behavior to call the appropriate Objective-C method. The only way you could take advantage of the fact that the methods are already defined in Objective-C and avoid hard coding the routing logic, would be using #selectors or similar dynamic function calling which is available in Objective-C. However, this is much more complicated to implement and probably presents a security risk. I would recommend implementing the routing logic like is shown in the code above.
PhoneGap has functionality for adding native plugins, to add a Flurry log event plugin for iOS I would do something like this:
Add a PGFlurry PhoneGap plugin class:
PGFlurry.h
#import <PhoneGap/PGPlugin.h>
#interface PGFlurry : PGPlugin
- (void)logEvent:(NSArray*)arguments withDict:(NSDictionary*)options;
#end
PGFlurry.m
#import "PGFlurry.h"
#import "FlurryAPI.h"
#implementation PGFlurry
// if you init Flurry somewhere else you can remove this method
- (PGPlugin*) initWithWebView:(UIWebView*)theWebView {
self = [super init];
if (self == nil) {
return nil;
}
// init and start Flurry
[FlurryAPI startSession:#"API key"];
return self;
}
- (void)logEvent:(NSArray*)arguments withDict:(NSDictionary*)options {
[FlurryAPI logEvent:[arguments objectAtIndex:0]];
}
#end
Add a JavaScript plugin helper to the www folder:
Flurry.js
PhoneGap.addConstructor(function() {
if(!window.plugins) {
window.plugins = {};
}
window.plugins.flurry = {
logEvent: function(name) {
return PhoneGap.exec("PGFlurry.logEvent", name);
}
}
});
Add the plugin to PhoneGap.plist by adding a key/value pair with both the key and value being "PGFlurry" to the "plugins" dictionary.
Now you should be able to use it like this:
<!DOCTYPE html>
<html>
<head>
<script src="phonegap.js" type="text/javascript"></script>
<script src="flurry.js" type="text/javascript"></script>
<script type="text/javascript">
document.addEventListener("deviceready", function() {
window.plugins.flurry.logEvent("Testing");
}, false);
</script>
</head>
<body>
</body>
</html>
You can find the Phonegap Flurry Plugin written by me at
https://github.com/designerkamal/Phonegap-Flurry-Plugin
Don't use their objective-c library, use their js library and you won't have to worry about objective-c. :)

Categories

Resources