Writing a Cordova/Ionic Plugin in Swift - ios

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??

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.

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.

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");
}
);
});

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.

Related

Xcode project fails to find the 'AmplifyModels' file that is generated for iOS project using AWS Amplify DataStore

Recently, I was looking to begin an iOS project that uses the DataStore service of AWS Amplify. To get familiar, I followed the 'Getting Started' documentation found at https://sandbox.amplifyapp.com/start#datastore and accepted all the defaults to create a simple Blog application.
When I was in the 'Test' stage and on the last part of step 4, my Xcode project was failing to find 'AmplifyModels' even though they were generated successfully and exist in the /amplify/generated/models folder.
This is the code
import SwiftUI
import Amplify
import AmplifyPlugins
#main
struct BlogApp: App {
public init() {
let dataStorePlugin = AWSDataStorePlugin(modelRegistration: AmplifyModels())
//let apiPlugin = AWSAPIPlugin(modelRegistration: AmplifyModels()) // UNCOMMENT this line once backend is deployed
do {
try Amplify.add(plugin: dataStorePlugin)
//try Amplify.add(plugin: apiPlugin) // UNCOMMENT this line once backend is deployed
try Amplify.configure()
print("Initialized Amplify");
} catch {
print("Could not initialize Amplify: \(error)")
}
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
This is the error
Cannot find 'AmplifyModels' in scope
Steps taken to try and resolve the error
Clean and build Xcode project, along with quitting and restarting Xcode
Manually adding the files to the project in Xcode. The code was able to find the files only if I removed them from the 'amplify' folder and placed them in the same folder as BlogApp and ContentView, but that cannot be correct I imagine.
Cleared the DerivedData for the project and rebuilt.
I am running on Xcode 12.2. The reason that I decided to follow the 'Getting Started' documentation step by step is because I was failing with this same error for my own project I was creating, so I wanted to make sure that it was not some simple user error on my part.
Thank you in advance!
The instructions leave out one small step. You need to add the generated source files to the project so they'll get compiled.
pick menu "File > Add Files to ..."
select the amplify folder, and pick the 'Create groups' option

Unable to integrate ZXingObjC in a iOS Swift Project

Im working on an iOS project, which shows the customer number in a barcode. I had installed the framework ZXingObjC with CocoaPods, described in GitHub.
I can compile my Project without errors. I can also use the classes of ZXingObjC in my Objective-C classes, without errors. After than, I have added the import Command #import <ZXingObjC/ZXingObjC.h> to my bridging header file, like my other custom objective-c classes, without compile errors. (I had testet the header file by destroying some import statements and got the expected file not found exception.)
But now, I can't use any class of ZXingObjC in my swift classes. I only got the following compile error: Use of undeclared type '...'. The Xcode autocomplete is not working, too.
e.g.
var test : ZXMultiFormatWriter?
>> Use of undeclared type 'ZXMultiFormatWriter'
I tried:
setup new project, same issue
checked header search path: $(SRCROOT)/Pods/Headers/Public/Adjust
reinstalled the ZXingObjC framework
checked build settings: Enable Modules: YES
checked build settings: Other Linker Flags: $(inherited) -ObjC
-framework "ZXingObjC"
checked linked binaries in the build phases: framework is added
checked import statement in the bridging header file (#import
<ZXingObjC/ZXingObjC.h> and #import "ZXingObjC/ZXingObjC.h" -- no
difference)
Windows style: restarting Xcode and Mac ;-)
I'm using:
Xcode: 6.3.2
CocoaPods: 0.37.2
Project Deployment target: iOS 8.0
SDK: 8.3
Does anyone know the problem? Can anyone help?
How can I make the ZXingObjC framework available in swift?
Actually it is an easy issue:
Podfile
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
use_frameworks!
pod 'ZXingObjC', '~> 3.1'
So, on terminal:
cd workspace
pod install
Then, once opened project on Xcode, you have to edit bridging-header adding ZXingObj:
#import <ZXingObjC/ZXingObjC.h>
Finally, in your swift classes that uses ZXingObjC, you have to import ZXingObjC.
import ZXingObjC
class ZXingObjCWrapper {
func encode() {
let writer = ZXMultiFormatWriter.writer()
....
}
}
The rest of the code for when you need to set an UIImage with this bar code:
func generateDataMatrixQRCode(from string: String) -> UIImage? {
do {
let writer = ZXMultiFormatWriter()
let hints = ZXEncodeHints() as ZXEncodeHints
let result = try writer.encode(string, format: kBarcodeFormatDataMatrix, width: 1000, height: 1000, hints: hints)
if let imageRef = ZXImage.init(matrix: result) {
if let image = imageRef.cgimage {
return UIImage.init(cgImage: image)
}
}
}
catch {
print(error)
}
return nil
}
The header search path was not correct in my project. The right values are:
$(inherited)
"${PODS_ROOT}/Headers/Public"
"${PODS_ROOT}/Headers/Public/ZXingObjC"
The second and third lines were not added by installation with CocoaPods.
EDIT: The installed framework have to be added to "Embedded Binaries" in General tab of the project.
I tried everything on this page to add ZXingObjC as a Pod. My goal was to generate an Aztec barcode.
I checked my Header Search Path. As Reddas said, I had to manually add "${PODS_ROOT}/Headers/Public/ZXingObjC". I also added ZXingObjC as an Embedded Binary (in the General Tab).
I checked my bridging file & all was good. I checked my view controllers where I wanted to generate the barcode. The import ZXingObjC was there.
No compile errors. But I can't declare a variable of ZXingObjC.
No luck. Any more suggestions?
EDIT - I went into the Targets, Build Settings and searched for Header Search Paths. I added in BOTH "${PODS_ROOT}/Headers/Public/ZXingObjC" and "${PODS_ROOT}/Headers/Private/ZXingObjC"
This seemed to unclog whatever broke. It works now. Strangely, I can now even delete those entries and it works.

Install Realm in a Swift App

I am trying to add Realm to my app written in swift. I have followed the tutorial and I can't seem to get it to work. The biggest problem is that when I try to import Realm I get No such module 'Realm' I don't know what else to try. You can see my efforts below.
You can see the instructions here: http://realm.io/docs/cocoa/0.85.0/#swft
I have also copied the instructions below:
Due to the current lack of proper infrastructure for Swift dependency management, using Realm in your project requires the following steps:
Add Realm as a submodule by opening the Terminal, cd-ing into your top-level project directory, and entering the command git submodule add git#github.com:realm/realm-cocoa.git
Open the realm-cocoa folder, and drag Realm.xcodeproj into the file navigator of your Xcode project.
In Xcode, navigate to the target configuration window by clicking on the blue project icon, and selecting the application target under the “Targets” section in the sidebar.
In the tab bar at the top of that window, open the “Build Phases” panel.
Expand the “Target Dependencies” gorup, and add Realm’s iOS framework.
Expand the “Link Binary with Libraries” group, and add Realm’s iOS framework as well as libc++.dylib.
Click on the + button at the top left of the panel and select “New Copy Files Phase”. Rename this new phase to “Copy Frameworks”, set the “Destination” to “Frameworks”, and add Realm.framework.
Drag the file at realm-cocoa/Realm/Swift/RLMSupport.swift into the file navigator of your Xcode project, unchecking the “Copy items if needed” checkbox.
Below is what it looks like in my project:
I am not sure exactly why this isn't working, but here is a workaround:
Follow the latest instructions.
Create a bridging header, for example by
Add a new Objective-C class to your xcode project.
Agree to have a bridging header created
Delete the Objective-C class
Add this in the bridging header:
#import "Realm/Realm.h"
Remove any Import Realm statements from your code, including from RLMSupport.swift
Now it should work. For example, I test with putting this in my ViewController.swift
import UIKit
class Person: RLMObject {
dynamic var name = ""
dynamic var birthdate = NSDate(timeIntervalSince1970: 1)
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let author = Person()
author.name = "David Foster Wallace"
// Get the default Realm
let realm = RLMRealm.defaultRealm()
// Add to the Realm inside a transaction
realm.beginWriteTransaction()
realm.addObject(author)
realm.commitWriteTransaction()
// Print all Persons
println(Person.allObjects())
}
}
Which prints:
RLMArray <0x7a243760> (
[0] Person {
name = David Foster Wallace;
birthdate = 1970-01-01 00:00:01 +0000;
}
)
I have been talking with the guys at Realm, and it turns out that the latest instructions don't work with Realm <= 0.85 They changed they way the build the framework and it won't work anymore. They said they will release 0.86 later today that should fix the problems anyone is having with Swift. In the meantime I have a test project that anyone can take the latest framework from. https://github.com/smitt04/testRealm
Version 0.86 is now out and this is no longer an issue.
The Swift installation instructions were long and convoluted, so I'm not surprised you and several other users ran into issues.
Please follow the latest installation instructions here.

class method +setLoggingEnabled: not found

I am using Google Cloud Endpoint with iOS. I am trying to create the service object per the instructions at https://developers.google.com/appengine/docs/java/endpoints/consume_ios#Java_Creating_the_service_object. For the line of code [GTMHTTPFetcher setLoggingEnabled:YES]; xCode is showing the warning
class method '+setLoggingEnabled:' not found (return type defaults to 'id')
But when I look inside the .h file for GTMHTTPFetcher I can actually see the method as
#if STRIP_GTM_FETCH_LOGGING
// if logging is stripped, provide a stub for the main method
// for controlling logging
+ (void)setLoggingEnabled:(BOOL)flag;
#endif // STRIP_GTM_FETCH_LOGGING
and in the .m file it looks like this
#if STRIP_GTM_FETCH_LOGGING
+ (void)setLoggingEnabled:(BOOL)flag {
}
#endif // STRIP_GTM_FETCH_LOGGING
Also the class is generated by Google so...it should work (?)
Set as follows :
#define STRIP_GTM_FETCH_LOGGING 1
In the file that contains the following line of code:
[GTMHTTPFetcher setLoggingEnabled:YES];
Add the following importation:
#import "GTMHTTPFetcherLogging.h"
I just faced the same problem.
In my case, it was because I was using the GTMHTTPFetcher from the Google+ iOS SDK:
Adding required files to your iOS project (Point 2.)
I had set the header information as described (Copied below here for simplicity):
If you are using the Google+ iOS SDK, set up your Xcode project as follows:
In your Xcode project, go to the settings page for the target.
In the Build Settings tab, add the following item to Header Search Paths:
GOOGLE_PLUS_SDK_DIRECTORY/GoogleOpenSource.framework/Headers where
GOOGLE_PLUS_SDK_DIRECTORY is the directory where you installed the
Google+ iOS SDK.
(I have Google+ iOS SDK within my project so I am using: $(PROJECT_DIR)/MyProject/External/GoogleOpenSource.framework/Headers)
However to see the setLoggingEnabled method you will need to add -ObjC -all_load to Other Linker Flags

phonegap plugin barcodescanner not working on iOS

We are developing application for phonegap (2.4.0) - iOS (6) and Android platforms. Our app is using BarcodeScanner plugin.
Plugin is working without any problems on Android, but we have detected some problems on iOS.
At first in the file CDVBarcodeScanner.mm CDVPlugin.h was not found, so we commented if/else check around this line: #import <CORDOVA/CDVPlugin.h> and to config.xml file we have added this line: <plugin name="org.apache.cordova.barcodeScanner" value="CDVBarcodeScanner" />
Then was build successfull without any errors, but after clicking on scan button nothing happened and nothing was written to output.
Then we compared barcodescanner.js for Android and iOS and found that after adding logs at the end of Android js file:
if(!window.plugins) {
console.log("something 1");
window.plugins = {};
}
if (!window.plugins.barcodeScanner) {
window.plugins.barcodeScanner = new BarcodeScanner();
console.log("something 2");
}
output was written after starting of application. In the case of iOS we have also added to similar part some console logs:
(function() {
if (!window.plugins) window.plugins = {}
console.log("something 1");
if (!window.plugins.barcodeScanner) {
window.plugins.barcodeScanner = new BarcodeScanner();
console.log("something 2");
}
else {
console.log("Not installing barcodeScanner: window.plugins.barcodeScanner already exists")
}
})
but no one was written.
Thanks for every advice.
We have tried these changes and plugin works:
we downgraded to phonegap 2.3
we set -O0 compile flags to zxing-all-in-one.cpp
checked all added libraries

Resources