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 plugin with barcode scanner

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:

cordova ios barcode preview

cordova ios barcode result

Source Code

https://github.com/dynamsoft-dbr/phonegap-plugin-dbr