iOS native runtime implementation for Capacitor that bridges JavaScript and native iOS APIs.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Comprehensive plugin development framework for creating custom native functionality accessible from JavaScript.
Base class that all Capacitor plugins must inherit from, providing core functionality for JavaScript-to-native communication.
/**
* Base class for all Capacitor plugins providing core functionality
*/
@interface CAPPlugin : NSObject
// MARK: - Core Properties
/** Reference to the webview for direct access */
@property (nonatomic, weak, nullable) WKWebView *webView;
/** Reference to the bridge for accessing bridge functionality */
@property (nonatomic, weak, nullable) id<CAPBridgeProtocol> bridge;
/** Unique identifier for this plugin instance */
@property (nonatomic, strong, nonnull) NSString *pluginId;
/** Display name for this plugin */
@property (nonatomic, strong, nonnull) NSString *pluginName;
/** Registry of event listeners organized by event name */
@property (nonatomic, strong, nullable) NSMutableDictionary<NSString *, NSMutableArray<CAPPluginCall *>*> *eventListeners;
/** Cached event arguments for listeners that join after events are fired */
@property (nonatomic, strong, nullable) NSMutableDictionary<NSString *, NSMutableArray<id> *> *retainedEventArguments;
/** Whether to stringify Date objects in call responses */
@property (nonatomic, assign) BOOL shouldStringifyDatesInCalls;
// MARK: - Lifecycle Methods
/**
* Called after plugin initialization to perform setup
* Override this method instead of init() for plugin-specific initialization
*/
- (void)load;
/**
* Get the plugin identifier
* @returns Plugin identifier string
*/
-(NSString* _Nonnull)getId;
// MARK: - Event Handling
/**
* Register an event listener for a specific event
* @param eventName Name of the event to listen for
* @param listener Plugin call that will receive event notifications
*/
- (void)addEventListener:(NSString* _Nonnull)eventName listener:(CAPPluginCall* _Nonnull)listener;
/**
* Remove an event listener for a specific event
* @param eventName Name of the event
* @param listener Plugin call to remove
*/
- (void)removeEventListener:(NSString* _Nonnull)eventName listener:(CAPPluginCall* _Nonnull)listener;
/**
* Notify all listeners of an event with data
* @param eventName Name of the event to fire
* @param data Event data to send to listeners
*/
- (void)notifyListeners:(NSString* _Nonnull)eventName data:(NSDictionary<NSString *, id>* _Nullable)data;
/**
* Notify listeners with optional data retention for late joiners
* @param eventName Name of the event to fire
* @param data Event data to send to listeners
* @param retain Whether to retain data for listeners that join later
*/
- (void)notifyListeners:(NSString* _Nonnull)eventName data:(NSDictionary<NSString *, id>* _Nullable)data retainUntilConsumed:(BOOL)retain;
/**
* Get all listeners for a specific event
* @param eventName Name of the event
* @returns Array of plugin calls listening to the event
*/
- (NSArray<CAPPluginCall *>* _Nullable)getListeners:(NSString* _Nonnull)eventName;
/**
* Check if an event has any listeners
* @param eventName Name of the event
* @returns YES if event has listeners, NO otherwise
*/
- (BOOL)hasListeners:(NSString* _Nonnull)eventName;
/**
* Add a listener using the standard addEventListener pattern
* @param call Plugin call containing listener details
*/
- (void)addListener:(CAPPluginCall* _Nonnull)call;
/**
* Remove a listener using the standard removeEventListener pattern
* @param call Plugin call containing listener details
*/
- (void)removeListener:(CAPPluginCall* _Nonnull)call;
/**
* Remove all listeners for all events
* @param call Plugin call (response sent to this call)
*/
- (void)removeAllListeners:(CAPPluginCall* _Nonnull)call;
// MARK: - Permission Handling (Capacitor 3.0+)
/**
* Check current permissions for this plugin
* Override to implement plugin-specific permission checking
* @param call Plugin call to respond with permission status
*/
- (void)checkPermissions:(CAPPluginCall* _Nonnull)call;
/**
* Request permissions from the user
* Override to implement plugin-specific permission requests
* @param call Plugin call to respond with permission result
*/
- (void)requestPermissions:(CAPPluginCall* _Nonnull)call;
// MARK: - Navigation Control
/**
* Control webview navigation behavior
* @param navigationAction Navigation action being attempted
* @returns NSNumber with BOOL value: YES to block navigation, NO to allow, nil for default behavior
*/
- (NSNumber* _Nullable)shouldOverrideLoad:(WKNavigationAction* _Nonnull)navigationAction;
// MARK: - Configuration Access
/**
* Get plugin configuration object
* @returns PluginConfig instance with plugin-specific settings
*/
-(PluginConfig* _Nonnull)getConfig;
// MARK: - UI Utilities
/**
* Present a popover centered on screen
* @param vc View controller to present as popover
*/
-(void)setCenteredPopover:(UIViewController* _Nonnull) vc;
/**
* Present a popover centered on screen with specific size
* @param vc View controller to present as popover
* @param size Size for the popover
*/
-(void)setCenteredPopover:(UIViewController* _Nonnull) vc size:(CGSize) size;
@endUsage Examples:
import Capacitor
class MyPlugin: CAPPlugin {
override func load() {
super.load()
// Plugin initialization
let config = getConfig()
let apiKey = config.getString("apiKey", "default")
setupPlugin(apiKey: apiKey)
}
@objc func doSomething(_ call: CAPPluginCall) {
let message = call.getString("message", "Hello")
// Perform native work
performNativeTask(message: message) { result in
call.resolve(["result": result])
}
}
@objc func startListening(_ call: CAPPluginCall) {
// Add event listener
addEventListener("dataChanged", listener: call)
call.resolve()
}
@objc func checkPermissions(_ call: CAPPluginCall) {
let status = checkCameraPermission()
call.resolve(["camera": status])
}
@objc func requestPermissions(_ call: CAPPluginCall) {
requestCameraPermission { granted in
call.resolve(["camera": granted ? "granted" : "denied"])
}
}
private func performNativeTask(message: String, completion: @escaping (String) -> Void) {
// Simulate async work
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
completion("Processed: \(message)")
// Notify listeners
self.notifyListeners("dataChanged", data: ["newData": "Updated value"])
}
}
}Protocol that plugins must conform to for automatic discovery and method registration.
/**
* Protocol for plugin metadata and method registration
*/
@protocol CAPBridgedPlugin <NSObject>
/** Unique identifier for the plugin */
@property (nonnull, readonly) NSString *identifier;
/** JavaScript interface name */
@property (nonnull, readonly) NSString *jsName;
/** Array of plugin methods available to JavaScript */
@property (nonnull, readonly) NSArray<CAPPluginMethod *> *pluginMethods;
@endPlugin Registration Macros:
/**
* Plugin configuration macro
* @param plugin_id Objective-C class name
* @param js_name JavaScript interface name
*/
#define CAP_PLUGIN_CONFIG(plugin_id, js_name) \
- (NSString *)identifier { return @#plugin_id; } \
- (NSString *)jsName { return @js_name; }
/**
* Plugin method registration macro
* @param method_name Objective-C method name
* @param method_return_type Return type (promise/callback/none)
*/
#define CAP_PLUGIN_METHOD(method_name, method_return_type) \
[methods addObject:[[CAPPluginMethod alloc] initWithName:@#method_name returnType:method_return_type]]
/**
* Complete plugin definition macro
* @param objc_name Objective-C class name
* @param js_name JavaScript interface name
* @param methods_body Block defining plugin methods
*/
#define CAP_PLUGIN(objc_name, js_name, methods_body) \
@interface objc_name : NSObject \
@end \
@interface objc_name (CAPPluginCategory) <CAPBridgedPlugin> \
@end \
@implementation objc_name (CAPPluginCategory) \
- (NSArray *)pluginMethods { \
NSMutableArray *methods = [NSMutableArray new]; \
methods_body \
return methods; \
} \
CAP_PLUGIN_CONFIG(objc_name, js_name) \
@endReturn Type Constants:
/** Method returns no value */
#define CAPPluginReturnNone @"none"
/** Method uses callback pattern */
#define CAPPluginReturnCallback @"callback"
/** Method returns a promise */
#define CAPPluginReturnPromise @"promise"Usage Examples:
// Swift plugin implementation
class MyPlugin: CAPPlugin {
@objc func echo(_ call: CAPPluginCall) {
let message = call.getString("message", "")
call.resolve(["message": message])
}
@objc func openSettings(_ call: CAPPluginCall) {
DispatchQueue.main.async {
if let settingsUrl = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(settingsUrl)
}
}
call.resolve()
}
}
// Objective-C plugin registration
CAP_PLUGIN(MyPlugin, MyPlugin,
CAP_PLUGIN_METHOD(echo, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(openSettings, CAPPluginReturnPromise);
)Class representing plugin method metadata for registration and invocation.
/**
* Plugin method metadata for registration and invocation
*/
@objc(CAPPluginMethod)
public class CAPPluginMethod: NSObject {
/**
* Initialize a plugin method
* @param name Method name as called from JavaScript
* @param returnType How the method returns values (promise/callback/none)
*/
public init(name: String, returnType: CAPPluginReturnType)
/** Method name for JavaScript calls */
public let name: String
/** Objective-C selector for the method */
public let selector: Selector
/** Return type defining how method responds */
public let returnType: CAPPluginReturnType
}
/**
* Plugin method return type enumeration
*/
public enum CAPPluginReturnType: Int {
/** Method returns no value */
case none
/** Method uses callback pattern */
case callback
/** Method returns promise */
case promise
}Manual Method Registration:
class MyPlugin: CAPPlugin {
// Manual method registration (alternative to macros)
override class func pluginMethods() -> [CAPPluginMethod] {
return [
CAPPluginMethod(name: "echo", returnType: .promise),
CAPPluginMethod(name: "openSettings", returnType: .promise),
CAPPluginMethod(name: "startListening", returnType: .callback)
]
}
}How plugins are registered with the bridge and initialized.
// Registration with bridge
override func viewDidLoad() {
super.viewDidLoad()
}
override func capacitorDidLoad() {
super.capacitorDidLoad()
// Register plugins after bridge is ready
bridge?.registerPluginType(MyPlugin.self)
bridge?.registerPluginType(AnotherPlugin.self)
}
// Alternative: Register during bridge initialization
override func instanceDescriptor() -> InstanceDescriptor {
let descriptor = InstanceDescriptor()
// Plugins can be configured via JSON as well
descriptor.pluginConfigurations = [
"MyPlugin": [
"apiKey": "your-api-key",
"enabled": true,
"timeout": 30
]
]
return descriptor
}Common patterns for complex plugin development.
Event-Driven Plugin:
class SensorPlugin: CAPPlugin {
private var sensorManager: SensorManager?
override func load() {
super.load()
sensorManager = SensorManager()
sensorManager?.delegate = self
}
@objc func startSensing(_ call: CAPPluginCall) {
let interval = call.getDouble("interval", 1000) // milliseconds
addEventListener("sensorData", listener: call)
sensorManager?.start(interval: interval / 1000.0)
call.resolve()
}
@objc func stopSensing(_ call: CAPPluginCall) {
sensorManager?.stop()
call.resolve()
}
}
extension SensorPlugin: SensorManagerDelegate {
func sensorDidUpdate(data: [String: Any]) {
notifyListeners("sensorData", data: data)
}
}Permission-Aware Plugin:
class LocationPlugin: CAPPlugin {
@objc override func checkPermissions(_ call: CAPPluginCall) {
let status = CLLocationManager.authorizationStatus()
let result = ["location": locationStatusToString(status)]
call.resolve(result)
}
@objc override func requestPermissions(_ call: CAPPluginCall) {
let manager = CLLocationManager()
manager.requestWhenInUseAuthorization()
// Wait for authorization result
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.checkPermissions(call)
}
}
@objc func getCurrentPosition(_ call: CAPPluginCall) {
guard CLLocationManager.authorizationStatus() == .authorizedWhenInUse ||
CLLocationManager.authorizationStatus() == .authorizedAlways else {
call.reject("Location permission not granted")
return
}
// Get location...
}
}Install with Tessl CLI
npx tessl i tessl/npm-capacitor--ios