Building Capacitor plugins for iOS involves writing Swift (or Objective-C) to interface with Apple’s iOS SDKs.
Capacitor embraces standard iOS development tools for building iOS plugins. We believe that using Swift (or, Objective-C) directly will make it easier to use existing solutions on Stack Overflow, share work with existing native developers, and use platform features as soon as they are made available.
To get started, first generate a plugin as shown in the Getting Started section of the Plugin guide.
Next, open
your-plugin/ios/Plugin.xcworkspace
in Xcode.
A Capacitor plugin for iOS is a simple Swift class that extends CAPPlugin
and
has some exported methods that will be callable from JavaScript.
Once your plugin is generated, you can start editing it by opening
Plugin.swift
.
In the generated example, there is a simple echo plugin with an echo
function that simply returns a value that it was given.
This example demonstrates a few core components of Capacitor plugins: receiving data from a Plugin Call, and returning data back to the caller:
Plugin.swift
import Capacitor
@objc(MyPlugin)
public class MyPlugin: CAPPlugin {
@objc override public func load() {
// Called when the plugin is first constructed in the bridge
}
@objc func echo(_ call: CAPPluginCall) {
let value = call.getString("value") ?? ""
call.resolve([
"value": value
])
}
}
Each plugin method receives an instance of CAPPluginCall
containing all the information of the plugin method invocation from the client.
A client can send any data that can be JSON serialized, such as numbers, text, booleans, objects, and arrays. This data
is accessible on the
options
field of the call instance, or by using convenience methods such as
getString
or getObject
.
For example, here is how you’d get data passed to your method:
@objc func storeContact(_ call: CAPPluginCall) {
let name = call.getString("yourName") ?? "default name"
let address = call.getObject("address") ?? [:]
let isAwesome = call.getBool("isAwesome") ?? false
guard let id = call.options["id"] as? String else {
call.reject("Must provide an id")
return
}
// ...
call.resolve()
}
Notice the various ways data can be accessed on the CAPPluginCall
instance, including how to require
options using
guard
.
A plugin call can succeed or fail. For calls using promises (most common), succeeding corresponds to calling
resolve
on the Promise, and failure calling
reject
. For those using callbacks, a succeeding will call the success callback or the error callback if failing.
The
resolve
method of
CAPPluginCall
takes a dictionary and supports JSON-serializable data types. Here’s an example of returning data back to the client:
call.resolve([
"added": true,
"info": [
"id": id
]
])
To fail, or reject a call, call call.reject
, passing an error string and (optionally) an
Error
instance and extra data back:
call.reject(error.localizedDescription, error, [
"item1": true
])
Plugins can override the load
method to run some code when the plugin is first initialized:
@objc(MyPlugin)
public class MyPlugin: CAPPlugin {
@objc override public func load() {
// Called when the plugin is first constructed in the bridge
}
}
To present a Native Screen over the Capacitor screen we need to acces the Capacitor’s View Controller.
To access the Capacitor’s View Controller, we have to use the
CAPBridge
object available on
CAPPlugin
class.
We can use the
UIViewController
to present Native View Controllers over it like this:
DispatchQueue.main.async {
self.bridge.viewController.present(ourCustomViewController, animated: true, completion: nil)
}
Using
DispatchQueue.main.async
makes your view render from the main thread instead of a background thread. Removing this can cause unexpected results.
On iPad devices you can also present
UIPopovers
, to do so, we provide a helper function to show it centered.
self.setCenteredPopover(ourCustomViewController)
self.bridge.viewController.present(ourCustomViewController, animated: true, completion: nil)
Capacitor Plugins can emit App events and Plugin events
App Events are regular javascript events, like
window
or
document
events.
Capacitor provides all this functions to fire events:
//If you want to provide the target
self.bridge.triggerJSEvent(eventName: "myCustomEvent", target: "window")
self.bridge.triggerJSEvent(eventName: "myCustomEvent", target: "document", data: "my custom data")
// Window Events
self.bridge.triggerWindowJSEvent(eventName: "myCustomEvent")
self.bridge.triggerWindowJSEvent(eventName: "myCustomEvent", data: "my custom data")
// Document events
self.bridge.triggerDocumentJSEvent(eventName: "myCustomEvent")
self.bridge.triggerDocumentJSEvent(eventName: "myCustomEvent", data: "my custom data")
And to listen for it, just use regular javascript:
window.addEventListener('myCustomEvent', function () {
console.log('myCustomEvent was fired');
});
Plugins can emit their own events that you can listen by attaching a listener to the plugin Object like this:
Plugins.MyPlugin.addListener('myPluginEvent', (info: any) => {
console.log('myPluginEvent was fired');
});
To emit the event from the Swift plugin class you can do it like this:
self.notifyListeners("myPluginEvent", data: [:])
To remove a listener from the plugin object:
const myPluginEventListener = Plugins.MyPlugin.addListener(
'myPluginEvent',
(info: any) => {
console.log('myPluginEvent was fired');
},
);
myPluginEventListener.remove();
Capacitor plugins can override the webview navigation. For that the plugin can override
- (NSNumber *)shouldOverrideLoad:(WKNavigationAction *)navigationAction
method.
Returning
true
causes the WebView to abort loading the URL.
Returning
false
causes the WebView to continue loading the URL.
Returning
nil
will defer to the default Capacitor policy.
To make sure Capacitor can see your plugin, you must do two things: export your Swift class to Objective-C, and register it using the provided Capacitor Objective-C Macros.
To export your Swift class to Objective-C, make sure to add
@objc(MyPlugin)
above your Swift class, and add
@objc
before any plugin method, as shown above.
To register your plugin with Capacitor, you’ll need to create a new Objective-C file (with a
.m
extension, not .h
!) corresponding to your plugin (such as MyPlugin.m
) and use the Capacitor macros to register the plugin, and each method that you will use. Important: you
must use the New File dialog in Xcode to do this. You’ll then be prompted by Xcode to create a Bridging Header, which you
must do.
Finally, register the plugin by adding the required Capacitor plugin macros into your new
.m
file:
#import <Capacitor/Capacitor.h>
CAP_PLUGIN(MyPlugin, "MyPlugin",
CAP_PLUGIN_METHOD(echo, CAPPluginReturnPromise);
)
This makes
MyPlugin
, and the
echo
method available to the Capacitor web runtime, indicating to Capacitor that the echo method will return a Promise.