With Capacitor, you are encouraged to write Swift or Objective-C code to implement the native features your app needs.
There may not be a Capacitor plugin for everything–and that’s okay! It is possible to write WebView-accessible native code right in your app.
The easiest way to communicate between JavaScript and native code is to build a custom Capacitor plugin that is local to your app.
EchoPlugin.swift
First, create a EchoPlugin.swift
file by
opening Xcode, right-clicking on the
App group (under the
App target), selecting
New File… from the context menu, choosing
Swift File in the window, and creating the file.
Copy the following Swift code into
EchoPlugin.swift
:
import Capacitor
@objc(EchoPlugin)
public class EchoPlugin: CAPPlugin {
@objc func echo(_ call: CAPPluginCall) {
let value = call.getString("value") ?? ""
call.resolve(["value": value])
}
}
The
@objc
decorators are required to make sure Capacitor’s runtime (which must use Objective-C for dynamic plugin support) can see it.
We must register custom plugins on both iOS and web so that Capacitor can bridge between Swift and JavaScript.
EchoPlugin.m
Next, create a EchoPlugin.m
file with Xcode in the same way, but choose
Objective-C in the window. Leave the
File Type as
Empty File. If prompted by Xcode to create a Bridging Header, click
Create Bridging Header.
Using Xcode to create native files is recommended because it ensures the references are added to the project appropriately.
These changes to project files should be committed to your project along with the new files themselves.
Copy the following Swift code into
EchoPlugin.m
:
#import <Capacitor/Capacitor.h>
CAP_PLUGIN(EchoPlugin, "Echo",
CAP_PLUGIN_METHOD(echo, CAPPluginReturnPromise);
)
These Objective-C macros register your plugin with Capacitor, making
EchoPlugin
and itsecho
method available to JavaScript. Whenever you add or remove methods inEchoPlugin.swift
, this file must be updated.
In JS, we use
registerPlugin()
from
@capacitor/core
to create an object which is linked to our Swift plugin.
import { registerPlugin } from '@capacitor/core';
const Echo = registerPlugin('Echo');
export default Echo;
The first parameter to
registerPlugin()
is the plugin name, which must match the second parameter to theCAP_PLUGIN
macro inEchoPlugin.m
.
TypeScript
We can define types on our linked object by defining an interface and using it in the call to
registerPlugin()
.
import { registerPlugin } from '@capacitor/core';
+export interface EchoPlugin {
+ echo(options: { value: string }): Promise<{ value: string }>;
+}
-const Echo = registerPlugin('Echo');
+const Echo = registerPlugin<EchoPlugin>('Echo');
export default Echo;
The generic parameter of
registerPlugin()
is what defines the structure of the linked object. You can use
registerPlugin<any>('Echo')
to ignore types if you need to. No judgment. ❤️
Use the exported
Echo
object to call your plugin methods. The following snippet will call into Swift on iOS and print the result:
import Echo from '../path/to/echo-plugin';
const { value } = await Echo.echo({ value: 'Hello World!' });
console.log('Response from native:', value);