How to Make Cordova Plugin with Custom iOS framework
Apache Cordova (formerly PhoneGap) is a mobile application development framework that enables developers to build mobile apps using CSS, JavaScript, and HTML. Different from Xamarin that converting C# code to native apps, Cordova composes apps with hybrid code - all UI layouts are rendered with web technologies via web views and native device APIs are invoked via plugins. In this article, I will demonstrate how to use Cordova plugin to build iOS app with third-party iOS framework.
Cordova Project
A Cordova project includes:
- Plugin Code
- Platform Code
- Configuration
- HTML, CSS, JS and assets
Cordova Plugin with Third Party iOS Framework
Let’s create a Cordova plugin with Dynamsoft iOS barcode SDK. Since there is an existing Cordova plugin BarcodeScanner, I can make some changes based on the source code:
git clone https://github.com/phonegap/phonegap-plugin-barcodescanner.git
Download and install Dynamsoft iOS Barcode SDK. Copy the DynamsoftBarcodeReader.framework to src/ios/. Edit plugin.xml:
<!-- ios -->
<platform name="ios">
<!-- Cordova >= 2.8 -->
<config-file target="config.xml" parent="/*">
<feature name="BarcodeScanner">
<param name="ios-package" value="CDVBarcodeScanner" />
</feature>
</config-file>
<resource-file src="src/ios/scannerOverlay.xib" />
<resource-file src="src/ios/CDVBarcodeScanner.bundle" />
<source-file src="src/ios/CDVBarcodeScanner.mm" compiler-flags="-fno-objc-arc" />
<framework src="libiconv.dylib" />
<framework src="AVFoundation.framework" />
<framework src="AssetsLibrary.framework" />
<framework src="CoreVideo.framework" />
<framework src="QuartzCore.framework" />
<framework src="CoreGraphics.framework" />
<framework src="CoreImage.framework" />
<framework src="AudioToolbox.framework" />
<framework src="src/ios/DynamsoftBarcodeReader.framework" custom="true"/>
</platform>
I replaced ZXing APIs with Dynamsoft Barcode Reader APIs.
Open CDVBarcodeScanner.mm and find:
- (void)captureOutput:(AVCaptureOutput*)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection*)connection
This is where we receive the video frame and detect barcode:
- (void)captureOutput:(AVCaptureOutput*)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection*)connection {
if (!self.capturing) return;
#if USE_SHUTTER
if (!self.viewController.shutterPressed) return;
self.viewController.shutterPressed = NO;
UIView* flashView = [[UIView alloc] initWithFrame:self.viewController.view.frame];
[flashView setBackgroundColor:[UIColor whiteColor]];
[self.viewController.view.window addSubview:flashView];
[UIView
animateWithDuration:.4f
animations:^{
[flashView setAlpha:0.f];
}
completion:^(BOOL finished){
[flashView removeFromSuperview];
}
];
// [self dumpImage: [[self getImageFromSample:sampleBuffer] autorelease]];
#endif
// Dynamsoft Barcode Reader SDK
@autoreleasepool {
void *imageData = NULL;
uint8_t *copyToAddress;
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
OSType pixelFormat = CVPixelBufferGetPixelFormatType(imageBuffer);
if (!(pixelFormat == '420v' || pixelFormat == '420f'))
{
return;
}
CVPixelBufferLockBaseAddress(imageBuffer, 0);
int numPlanes = (int)CVPixelBufferGetPlaneCount(imageBuffer);
int bufferSize = (int)CVPixelBufferGetDataSize(imageBuffer);
int imgWidth = (int)CVPixelBufferGetWidthOfPlane(imageBuffer, 0);
int imgHeight = (int)CVPixelBufferGetHeightOfPlane(imageBuffer, 0);
if(numPlanes < 1)
{
return;
}
uint8_t *baseAddress = (uint8_t *) CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0);
size_t bytesToCopy = CVPixelBufferGetHeightOfPlane(imageBuffer, 0) * CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0);
imageData = malloc(bytesToCopy);
copyToAddress = (uint8_t *) imageData;
memcpy(copyToAddress, baseAddress, bytesToCopy);
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
NSData *buffer = [NSData dataWithBytesNoCopy:imageData length:bufferSize freeWhenDone:YES];
// read frame using Dynamsoft Barcode Reader in async manner
ReadResult *result = [self.barcodeReader readSingle:buffer width:imgWidth height:imgHeight barcodeFormat: self.barcodeFormat];
if (result.barcodes != nil) {
Barcode *barcode = (Barcode *)result.barcodes[0];
[self barcodeScanSucceeded:barcode.displayValue format:barcode.formatString];
}
}
}
The preview data format needs to be initialized with NV21:
[output setVideoSettings:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange] forKey:(id)kCVPixelBufferPixelFormatTypeKey]];
Define and initialize Dynamsoft Barcode Reader:
#import <DynamsoftBarcodeReader/DynamsoftBarcodeReader.h>
#import <DynamsoftBarcodeReader/Barcode.h>
@property (nonatomic, retain) BarcodeReader* barcodeReader;
@property (nonatomic) long barcodeFormat;
@synthesize barcodeReader = _barcodeReader;
@synthesize barcodeFormat = _barcodeFormat;
- (id)initWithPlugin:(CDVBarcodeScanner*)plugin
callback:(NSString*)callback
parentViewController:(UIViewController*)parentViewController
alterateOverlayXib:(NSString *)alternateXib {
self = [super init];
if (!self) return self;
self.plugin = plugin;
self.callback = callback;
self.parentViewController = parentViewController;
self.alternateXib = alternateXib;
self.is1D = YES;
self.is2D = YES;
self.capturing = NO;
self.results = [NSMutableArray new];
CFURLRef soundFileURLRef = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR("CDVBarcodeScanner.bundle/beep"), CFSTR ("caf"), NULL);
AudioServicesCreateSystemSoundID(soundFileURLRef, &_soundFileObject);
self.barcodeReader = [[BarcodeReader alloc] initWithLicense:@"license"];
self.barcodeFormat = Barcode.OneD | Barcode.PDF417 | Barcode.QR_CODE;
return self;
}
Building iOS Barcode Scanner with Cordova Plugin
Initialize a new project:
cordova create barcode com.dynamsoft.barcode Barcode
Add iOS platform:
cd barcode
cordova platform add ios --save
Install the plugin made above:
cordova plugin add <local-path>/<plugin-name>
Import the project from platforms/ios to Xcode.
Create a button in index.html:
<div class="app">
<div id="deviceready">
<button id="scan">scan barcode</button>
</div>
</div>
Trigger barcode scanning event in index.js:
onDeviceReady: function() {
document.getElementById("scan").onclick = function() {
cordova.plugins.barcodeScanner.scan(
function (result) {
alert("We got a barcode\\n" +
"Result: " + result.text + "\\n" +
"Format: " + result.format + "\\n" +
"Cancelled: " + result.cancelled);
},
function (error) {
alert("Scanning failed: " + error);
},
{
"preferFrontCamera" : false, // iOS and Android
"showFlipCameraButton" : true, // iOS and Android
}
);
}
app.receivedEvent('deviceready');
},
Build and run the iOS project: