2
votes

I followed the instructions from this site, http://moduscreate.com/writing-a-cordova-plugin-in-swift-for-ios/, for creating my own cordova plugins for iOS platform. Firstly, I installed plugman and created a new plugin. This is my plugin.xml :

<?xml version='1.0' encoding='utf-8'?>
<plugin id="com-moduscreate-plugins-echoswift" version="0.0.1" xmlns="http://apache.org/cordova/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android">
<name>ModusEchoSwift</name>
<js-module name="ModusEchoSwift" src="www/ModusEchoSwift.js">
    <clobbers target="modusechoswift" />
</js-module>
<platform name="ios">
    <config-file target="config.xml" parent="/*">
        <feature name="ModusEchoSwift">
            <param name="ios-package" value="ModusEchoSwift" />
        </feature>
    </config-file>
    <source-file src="src/ios/ModusEchoSwift.swift" />
</platform>
</plugin>

Here is my js file for the plugin, i.e. ModusEchoSwift.js:

var exec = require('cordova/exec');
exports.echo = function(arg0, success, error) {
    exec(success, error, "ModusEchoSwift", "echo", [arg0]);
};
exports.echojs = function(arg0, success, error) {
    if (arg0 && typeof(arg0) === 'string' && arg0.length > 0) {
        success(arg0);
    } else {
        error('Empty message!');
    }
};

And this is my native Swift class, i.e. ModusEchoSwift.swift:

@objc(ModusEchoSwift) class ModusEchoSwift : CDVPlugin {
func echo(command: CDVInvokedUrlCommand) {
    var pluginResult = CDVPluginResult(
        status: CDVCommandStatus_ERROR
    )

    let msg = command.arguments[0] as? String ?? ""

    if msg.characters.count > 0 {
        /* UIAlertController is iOS 8 or newer only. */
        let toastController: UIAlertController = 
            UIAlertController(
                title: "", 
                message: msg, 
                preferredStyle: .Alert
            )

        self.viewController?.presentViewController(
            toastController, 
            animated: true, 
            completion: nil
        )

        let duration = Double(NSEC_PER_SEC) * 3.0
        dispatch_after(
            dispatch_time(
                DISPATCH_TIME_NOW, 
                Int64(duration)
            ), 
            dispatch_get_main_queue(), 
            { 
                toastController.dismissViewControllerAnimated(
                    true, 
                    completion: nil
                )
            }
        )

        pluginResult = CDVPluginResult(
            status: CDVCommandStatus_OK,
            messageAsString: msg
        )
    }

    self.commandDelegate!.sendPluginResult(
        pluginResult, 
        callbackId: command.callbackId
    )
  }
}

These are the files in my plugin. Now I am trying to show a simple alert using this plugin in my ionic project. I have a simple index.html right now,where I wanna display the alert, here it is:

<ion-pane>
  <ion-header-bar class="bar-stable">
    <h1 class="title">Ionic Blank Starter</h1>
  </ion-header-bar>
  <ion-content>
    <div ng-controller="myController">
      Hello {{user}}
      </div>
  </ion-content>
</ion-pane>

And here is my controller :

.controller('myController', function($scope){
console.log("Ok");
$scope.user = "kAy";
ModusEchoSwift.echo('Plugin Ready!', function(msg) { 
   console.log("Success");
  },
  function(err) {
   console.log("Error");
  }
);

})

Here in this controller, I keep getting the error that ModusEchoSwift is not defined, which I do not understand! Can anyone tell me how to use the function of my plugin in my existing ionic project??

4
have you included cordova.js file in your HTML?Gandhi
Ya i have, n i dont think thats the issueKrazzie KAy
Is your plugin included in iOS platform plugins folder? Also under your project plugins folder, is your custom plugin entry available in ios.json file?Gandhi
I just checked n yes they are present!Krazzie KAy
Have you included deviceready event listener and invoked plugin code inside the event? In case of Ionic, it should be platform ready.Gandhi

4 Answers

2
votes

This does work correctly with ionic v2.2.1 & cordova v6.5.0, provided the steps outlined in Simon's blog post are followed, and with one minor change: Add this line of code in the App.component.ts file below the imports:

declare let modusechoswift;

The Bridging-Header.h file in the ionic app was under 'Other Sources' in my case. Simon's post refers to a different location, which initially put me off, but it was a non-issue. In fact, moving around that header file anywhere in my Xcode project did not appear to have any effect. I'm guessing XCode doesn't care where the file is as long as it's present.

1
votes

I am the author of the blog that you're following, I've checked all of your code that you posted and it all looks correct. It may be that you haven't yet added the bridging header plugin to your Cordova app project that I refer to in my post (quoted below).

In short I think you need to add the plugin referenced below which will help deal with the Objective C to Swift bridging header needed. Also check you have the Modus plugin and the bridging one installed correctly, by doing

cordova plugin ls

In your app project's main folder (the one with config.xml in it). This should return both the ModusEchoSwift and the bridging header plugin in its output if both are correctly installed in the app project.

The Cordova plugin that we have been working on is implemented in Swift, but the native parts of a Cordova iOS app are written in Objective-C still. Normally, this isn’t code that we need to be concerned with, as the Cordova CLI auto generates it for us along with an Xcode project to compile and build it.

When using Swift to write a plugin however, we do need to modify the Cordova app’s Xcode project settings to allow our Swift plugin code to access the Cordova objects that it needs, which are written in Objective-C. To do this, we make use of a bridging header file.

A bridging header file is an Objective-C header that contains imports for each Objective-C header that we want to be able to access in our Swift code. In our case, we need access to some of the Cordova objects: CDVPlugin, CDVInvokedUrlCommand and CDVPluginResult for example. This means that our bridging header just needs to contain:

#import <Cordova/CDV.h> 

as our plugin won’t be using any other Objective-C references. CDV.h contains declarations for all of these, and is part of every Cordova app that the CLI generates.

Our bridging header needs to live in the folder platforms/ios/ in our Cordova app (e.g. for our TestApp that we’ll build next this would be platforms/ios/TestApp). It also needs to be referenced in the Xcode project file so that Xcode knows that this is a bridging header and that the Objective-C app project also contains Swift code when compiling and linking the application.

In Cordova, the platforms folder should ideally be a build artifact – generated entirely by executing Cordova CLI commands. We should not be editing or adding files manually in this folder. However, we now find ourselves needing to do so in order to insert the required bridging header into the Xcode project. Thankfully, Cordova’s CLI supports hooks that allow us to run scripts before or after certain CLI commands are executed, precisely to deal with situations such as this.

Hook scripts can be bundled up and distributed with plugins, making them easy to share and add to projects. GitHub user Alexis Kofman wrote a handy plugin that installs a hook script to automatically create the bridging header we need and configure the Xcode project to use it. His plugin can be found here and we’ll use it when configuring a test Cordova app to run our Swift plugin with.

For more information on mixing Objective C and Swift in the same project, we would recommend referring to the Apple documentation on the subject.

On further investigation it looks like you didn't add the bridging header plugin to your test app BEFORE adding the ios platform or the Swift plugin, this order of events is needed to get the bridging header plugin hook script to fire at the right time.

The following order of commands gets you to a working application from nothing, I just ran this on my local machine to triple check:

cordova create mytest
cd mytest
cordova plugin add cordova-plugin-add-swift-support --save
cordova platform add ios --save
cordova plugin add https://github.com/ModusCreateOrg/cordova-swift-plugin-example
cordova build ios
cordova emulate ios
  • Let emulator start.
  • Connect to emulator using Safari remote debugger.
  • Open JS console

In JS Console:

modusechoswift.echo('hello', undefined);

Observe a native 'toast' with 'hello' message appear from the plugin in the simulator then vanish soon after. Screenshot attached.enter image description here

1
votes

call controller 'modusechoswift' not 'ModusEchoSwift' and be sure platform is ready

 $ionicPlatform.ready(function() {
  modusechoswift.echo('Plugin Ready!', function(msg) {
      console.log("Success");
    },
    function(err) {
      console.log("Error");
    }
  );
});
0
votes

Usually the "plugin not defined" error means you need to expose the ModusCreate plugin via an Angular service (Ionic is built on AngularJS). Cordova itself doesn't use Angular, which is why that works but Ionic doesn't. There are a couple ways to make the service - I'd look at ngCordova for examples. ngCordova is most likely how cordova-plugin-camera is getting exposed to Angular already.

Once you've done that, you'll need to inject that service into your controller (in much the same way that $scope is already getting injected). After that you should be good to go. Hope that helps.