Phonegap Cordova call javascript functions from Objective-C - objective-c

I am facing some Objective-C lackiness of knowledge in my PhoneGap App.
I have a library that I need to implement in Objective-C. This Library has some callbacks that I receive in a delegate Class ( called CCController ) :
[MyLib sharedInstance].delegate = self;
This Class is instanciated in the AppDelegate.m like this :
CCController *myClass = [CCController alloc];
[myClass init];
Then, when my Lib sends events, the functions in my CCController are called.
I need, at this point, to call my Javascript functions with a parameter.
How can I implement this ?
I have tried calling a function in AppDelegate.m which contains this :
NSString* jsString = [NSString stringWithFormat:#"myJSFunction(\"%#\");", stringParameter];
[self.viewController.webView stringByEvaluatingJavaScriptFromString:jsString];
But without success, nothing is called in my JS...
How can I implement this ? Thanks for help :)

You have to create a plugin and put the native code there instead using it in the AppDelegate.m.
From the plugin class you can do this:
NSString* jsString = [NSString stringWithFormat:#"myJSFunction(\"%#\");", stringParameter];
[self.webView stringByEvaluatingJavaScriptFromString:jsString];
Plugin development guide

Related

How to call a cordova plugin(native iOS) method from another plugin?

I need to call method of (cordova-plugin-screen-orientation) in iOS class of my custom created cordova-plugin.
I am using cordova plugin (cordova-plugin-screen-orientation) to change orientation of my app.I am creating my custom plugin in which, I need to change my orientation on specific event.
#import "YoikScreenOrientation.h"
-(void)screenOrientation:(CDVInvokedUrlCommand *)command
{
//Block of code to change orientation
}
In my custom plugin myfile.m class,
import "YoikScreenOrientation.h"
CDVInvokedUrlCommand *obj = [CDVInvokedUrlCommand alloc];
NSArray *myArray = [NSArray arrayWithObjects:#"landscape", nil];
[obj.arguments arrayByAddingObjectsFromArray:myArray];
YoikScreenOrientation *YSO = [YoikScreenOrientation alloc];
[YSO screenOrientation:obj];
I expect command.arguments in screenOrientation method to get value "landscape", but it's getting nil.

Bug when unit testing Google Analytics tracker in XCode 6

I'm writing a unit test to check that the string I pass to the GAITracker class is being returned as the kGAIScreenName property for each screen.
However when I try to pass a sharedInstance to the GAI class to initialize the WNGoogleAnalyticsService instance I am getting the error Thread 1: EXC_BAD_ACCESS (code=1, address=0x20) as if it has not been allocated to the memory. No matter where I try to declare the sharedInstance won't initialize in the test class although it works fine in the AppDelegate.m.
WNGoogleAnlayticsServiceTest.m:
-(void)testIfNoScreenNameExists {
NSString *screenName = #"Screen";
Class builder = [GAIDictionaryBuilder class];
GAI *gai = [GAI sharedInstance];
WNGoogleAnalyticsService *s = [[WNGoogleAnalyticsService alloc] initWithGAInstance:gai
gaKey:#"test"
gaDictionaryBuilderClass:builder
debugging:NO];
id<GAITracker> tracker = [s trackerForScreen:screenName];
XCTAssertEqualObjects([tracker get:kGAIScreenName], screenName);
}
AppDelegate.m:
Add Google Analytics as analytics service
WNGoogleAnalyticsService *googleAnalyticsService = [[WNGoogleAnalyticsService alloc] initWithGAInstance:[GAI sharedInstance]
gaKey:[[NSBundle mainBundle] objectForInfoDictionaryKey:#"WNGoogleKey"]
gaDictionaryBuilderClass:[GAIDictionaryBuilder class]
debugging:analyticsDebugging];
I'm at a loss as to how to even go about fixing this bug so any help would be appreciated.
In line with the comment I left underneath my question, I feel I should formally answer this with my solution.
In the case of this unit test, I was trying to re-use a singleton by reusing the [GAI SharedInstance] method in both my WNGoogleAnlayticsServiceTest and in my AppDelegate.m file which, by definition, it cannot do.
So if you want to test the Google Analytics methods, you must use a tool like OCMock to do so as you can't initialise the sharedInstance twice.

Close main application if "helper" application closed

I have a wrapper application, which opens an other application, let's call it helper app. This app is inside the wrapper application Resource folder.
I would like to close the main application, after the helper app was closed. How can I achieve this?
I would like to also know, how can I quit the wrapper app, after the helper opened.
Are you developing for OSX (10.6 and later ) ?
In this case use NSRunningApplication to terminate the wrapper application.
#implementation AppDelegate // Helper Application delegate
- (void)applicationWillTerminate:(NSNotification *)aNotification
{
NSArray *apps = [NSRunningApplication runningApplicationsWithBundleIdentifier:#"com.yourApplication.Launcher"];
if ([apps count])
[(NSRunningApplication *)[apps objectAtIndex:0] terminate];
}
#end
And you can launch your Helper synchronously with NSWorkspace :
- (void)launchHelper // Wrapper launch method
{
NSURL *helperURL = [[NSBundle mainBundle] URLForResource:#"Helper" withExtension:#"app"];
if (helperURL)
{
NSError *err = nil;
[[NSWorkspace sharedWorkspace] launchApplicationAtURL:helprURL options:NSWorkspaceLaunchAllowingClassicStartup configuration:nil error:&err];
// here Helper 'main' have been called
}
}
short answer - you can't!
Every application work in sandbox mode. You can call other application but you lose control on your (of course you can use AppDelegate standard method to handle enter/back foreground/bacground actions but that's it).

Communication between Main Application and loadable bundle

OK, it's rather self-explanatory.
Let's say we've got our cocoa application.
Let's all assume that you've already got some "plugins", packaged as independent loadable bundles.
This is how bundles are currently being loaded (given that the plugin's "principal class" is actually an NSWindowController subclass :
// Load the bundle
NSString* bundlePath = [[NSBundle mainBundle] pathForResource:#"BundlePlugin"
ofType:#"bundle"]
NSBundle* b = [NSBundle bundleWithPath:bundlePath];
NSError* err = nil;
[b loadAndReturnError:&err];
if (!err)
{
// if everything goes fine, initialise the main controller
Class mainWindowControllerClass = [b principalClass];
id mainWindowController = [[mainWindowControllerClass alloc] initWithWindowNibName:#"PluginWindow"];
}
Now, here's the catch :
How do I "publish" some of my main app's objects to the plugin?
How do I make my plugin "know" about my main app's classes?
Is it even possible to actually establish some sort of communication between them, so that e.g. the plugin can interact with my main app? And if so, how?
SIDENOTES :
Could using Protocols eliminate the "unknown selector" errors and the need for extensive use of performSelector:withObject:?
Obviously I can set and get value to-and-from my newly created mainWindowController as long as they're defined as properties. But is this the most Cocoa-friendly way?

PhoneGap plugin: AudioEncode success callback never called

I'm using the AudioEncode plugin for PhoneGap (Cordova) on iOS. After updating a couple of lines for a newer version of Cordova, it appears to be correctly encoding wav files as m4a. In the Xcode console I see:
AVAssetExportSessionStatusCompleted
doing success callback
When I look at the file system on the phone, the wav file has indeed become a m4a. However, the success callback (where I upload the file to a server) is never run. This is the relevant code in the plugin:
-(void) doSuccessCallback:(NSString*)path {
NSLog(#"doing success callback");
NSString* jsCallback = [NSString stringWithFormat:#"%#(\"%#\");", self.successCallback, path];
[self writeJavascript: jsCallback];
[self.successCallback release];
}
My code in the app goes like this:
function encodeSuccess (path) {
console.log('Audio encoded to M4A! Preparing to upload...')
// file transfer code...
}
console.log('Preparing to encode audio file...')
window.plugins.AudioEncode.encodeAudio(entry.fullPath, encodeSuccess, fail)
I'm assuming the doSuccessCallback function in the plugin needs to be updated, but I don't have experience with Objective C or PhoneGap plugins, so I'm stuck at this point.
Any ideas?
UPDATE
In the Objective C function posted above, I tried logging self.successCallback, and it logged as <null>. Then I went up to the top of the main encodeAudio function, and the argument which is assigned to self.successCallback ([arguments objectAtIndex:1]) also logs as <null>. So, it seems to me that the callbacks are not being passed into the main function successfully.
This is the AudioEncode.h file, maybe someone can spot the problem here:
#interface AudioEncode : CDVPlugin {
NSString* successCallback;
NSString* failCallback;
}
#property (nonatomic, retain) NSString* successCallback;
#property (nonatomic, retain) NSString* failCallback;
- (void)encodeAudio:(NSArray*)arguments withDict:(NSDictionary*)options;
Ok, I figured this out by reading the basic examples in the Cordova Plugin Development Guide closely. The problem was with the ordering of parameters for cordova.exec(), which must have changed recently.
I plan to submit a pull request on GitHub with a working version of the plugin, but for now, here's the basic solution.
Before asking this question, I had updated the imports in AudioEncode.h from #import <PhoneGap/PGPlugin.h> to:
#import <Cordova/CDVPlugin.h>
#import <Cordova/CDVPluginResult.h>
Any reference to PGPlugin should also be updated to CDVPlugin, and PhoneGap should become cordova.
Here's the crux of the problem: in AudioEncode.js, cordova.exec() (where the original plugin calls PhoneGap.exec()) needs to be called like this:
AudioEncode.prototype.encodeAudio = function(audioPath, successCallback, failCallback) {
cordova.exec(successCallback, failCallback, "AudioEncode", "encodeAudio", [audioPath]);
};
If you don't order the parameters like this, the callbacks won't be passed in (although audioPath was...). Look at the docs for more details, but the parameters have to be the two callbacks first, the module name, the module action, and finally an array of extra parameters.
Then, you'll need to read in the parameters in the main encodeAudio function like this:
self.callback = [[arguments objectAtIndex:0] retain];
NSString* audioPath = [arguments objectAtIndex:1];
Note that there is only one callback object now, which contains references to the success and fail callbacks. This means that whenever the plugin sets up variables for successCallback and failCallback, you now only need callback (e.g. #synthesize callback). This is also declared in the AudioEncode.h file with #interface and #property.
Now, when actually firing the callbacks & returning data (in the doSuccessCallback and doFailCallback functions), you need to use CDVPluginResult, like this:
CDVPluginResult* pluginResult = nil;
NSString* javaScript = nil;
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:path];
javaScript = [pluginResult toSuccessCallbackString:self.callback];
[self writeJavascript: javaScript];
[self.callback release];
Until I get the updated module up on GitHub, this should help anyone to get the plugin working.