iOS Plugins
This section provides details for how to implement native plugin code on the iOS platform. Before reading this, see Application Plugins for an overview of the plugin's structure and its common JavaScript interface. This section continues to demonstrate the sample echo plugin that communicates from the Cordova webview to the native platform and back.
An iOS plugin is implemented as an Objective-C class that extends the
CDVPlugin
class. For JavaScript's exec
method's service
parameter to map to an Objective-C class, each plugin class must be
registered as a <feature>
tag in the named application directory's
config.xml
file.
Plugin Class Mapping
The JavaScript portion of a plugin uses the cordova.exec
method as
follows:
exec(<successFunction>, <failFunction>, <service>, <action>, [<args>]);
This marshals a request from the UIWebView
to the iOS native side,
effectively calling the action
method on the service
class, with
the arguments passed in the args
array.
Specify the plugin as a <feature>
tag in your Cordova-iOS
application's project's config.xml
file, using the plugin.xml
file
to inject this markup automatically, as described in Application
Plugins:
<feature name="LocalStorage">
<param name="ios-package" value="CDVLocalStorage" />
</feature>
The feature's name
attribute should match what you specify as the
JavaScript exec
call's service
parameter. The value
attribute
should match the name of the plugin's Objective-C class. The <param>
element's name
should always be ios-package
. If you do not follow
these guidelines, the plugin may compile, but Cordova may still not be
able to access it.
Plugin Initialization and Lifetime
One instance of a plugin object is created for the life of each
UIWebView
. Plugins are ordinarily instantiated when first referenced
by a call from JavaScript. Otherwise they can be instantiated by
setting a param
named onload
to true
in the config.xml
file:
<feature name="Echo">
<param name="ios-package" value="Echo" />
<param name="onload" value="true" />
</feature>
There is no designated initializer for plugins. Instead, plugins
should use the pluginInitialize
method for their startup logic.
Plugins with long-running requests, background activity such as media
playback, listeners, or that maintain internal state should implement
the onReset
method to clean up those activities. The method runs
when the UIWebView
navigates to a new page or refreshes, which
reloads the JavaScript.
Writing an iOS Cordova Plugin
A JavaScript call fires off a plugin request to the native side, and
the corresponding iOS Objective-C plugin is mapped properly in the
config.xml
file, but what does the final iOS Objective-C plugin
class look like? Whatever is dispatched to the plugin with
JavaScript's exec
function is passed into the corresponding plugin
class's action
method. A plugin method has this signature:
- (void)myMethod:(CDVInvokedUrlCommand*)command
{
CDVPluginResult* pluginResult = nil;
NSString* myarg = [command.arguments objectAtIndex:0];
if (myarg != nil) {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
} else {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Arg was null"];
}
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
For more details, see
[CDVInvokedUrlCommand.h](https://github.com/apache/cordova-ios/blob/master/CordovaLib/Classes/CDVInvokedUrlCommand.h)
,
[CDVPluginResult.h](https://github.com/apache/cordova-ios/blob/master/CordovaLib/Classes/CDVPluginResult.h)
,
and
[CDVCommandDelegate.h](https://github.com/apache/cordova-ios/blob/master/CordovaLib/Classes/CDVCommandDelegate.h)
.
iOS CDVPluginResult Message Types
You can use CDVPluginResult
to return a variety of result types back to
the JavaScript callbacks, using class methods that follow this pattern:
+ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAs...
You can create String
, Int
, Double
, Bool
, Array
,
Dictionary
, ArrayBuffer
, and Multipart
types. You can also leave
out any arguments to send a status, or return an error, or even choose
not to send any plugin result, in which case neither callback fires.
Note the following for complex return values:
-
messageAsArrayBuffer
expectsNSData*
and converts to anArrayBuffer
in the JavaScript callback. Likewise, anyArrayBuffer
the JavaScript sends to a plugin are converted toNSData*
. -
messageAsMultipart
expects anNSArray*
containing any of the other supported types, and sends the entire array as thearguments
to your JavaScript callback. This way, all of the arguments are serialized or deserialized as necessary, so it is safe to returnNSData*
as multipart, but not asArray
/Dictionary
.
Echo iOS Plugin Example
To match the JavaScript interface's echo feature described in
Application Plugins, use the plugin.xml
to inject a feature
specification to the local platform's config.xml
file:
<platform name="ios">
<config-file target="config.xml" parent="/*">
<feature name="Echo">
<param name="ios-package" value="Echo" />
</feature>
</config-file>
</platform>
Then we would add the following Echo.h
and Echo.m
files to the
Plugins
folder within the Cordova-iOS application directory:
/********* Echo.h Cordova Plugin Header *******/
#import <Cordova/CDV.h>
@interface Echo : CDVPlugin
- (void)echo:(CDVInvokedUrlCommand*)command;
@end
/********* Echo.m Cordova Plugin Implementation *******/
#import "Echo.h"
#import <Cordova/CDV.h>
@implementation Echo
- (void)echo:(CDVInvokedUrlCommand*)command
{
CDVPluginResult* pluginResult = nil;
NSString* echo = [command.arguments objectAtIndex:0];
if (echo != nil && [echo length] > 0) {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:echo];
} else {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR];
}
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
@end
The necessary imports at the top of the file extends the class from
CDVPlugin
. In this case, the plugin only supports a single echo
action. It obtains the echo string by calling the objectAtIndex
method get the first parameter of the arguments
array, which
corresponds to the arguments passed in by the JavaScript exec()
function.
It checks the parameter to make sure it is not nil
or an empty
string, returning a PluginResult
with an ERROR
status if so. If
the parameter passes the check, it returns a PluginResult
with an
OK
status, passing in the original echo
string. Finally, it sends
the result to self.commandDelegate
, which executes the exec
method's success or failure callbacks on the JavaScript side. If the
success callback is called, it passes in the echo
parameter.
iOS Integration
The CDVPlugin
class features other methods that your plugin can
override. For example, you can capture the pause
, resume
, app
terminate and handleOpenURL
events. See the
CDVPlugin.h
and
CDVPlugin.m
class for guidance.
Threading
Plugin methods ordinarily execute in the same thread as the main interface. If your plugin requires a great deal of processing or requires a blocking call, you should use a background thread. For example:
- (void)myPluginMethod:(CDVInvokedUrlCommand*)command
{
// Check command.arguments here.
[self.commandDelegate runInBackground:^{
NSString* payload = nil;
// Some blocking logic...
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:payload];
// The sendPluginResult method is thread-safe.
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}
Debugging iOS Plugins
To debug on the Objective-C side, you need Xcode's built-in debugger. For JavaScript, on iOS 5.0 you can use Weinre, an Apache Cordova Project or iWebInspector, a third-party utility. For iOS 6, you can attach Safari 6.0 to your app running within the iOS 6 Simulator.
Common Pitfalls
-
Don't forget to add your plugin's mapping to
config.xml
. If you forget, an error is logged in the Xcode console. -
Don't forget to add any hosts you connect to in the whitelist, as described in Domain Whitelist Guide. If you forget, an error is logged in the Xcode console.