Have you implemented any barcode reader software in Swift on Mac OS X? Since Dynamsoft released the 1D/2D barcode SDK for Mac, I was wondering how I can bridge the C dylib with Swift. Why Swift, not Objective-C? Because Apple is encouraging developers to migrate to the new programming language, it is important to master the new tool if you want to create more excellent apps for Mac and iOS in the future. Swift is totally new to me, and thus I spent two days googling relevant resources and finally figured out the solution.
Prerequisites
Using Swift with C
It is recommended to read the e-book Using Swift with Cocoa and Objective-C published by Apple in iBooks. To quickly get familiar with how to make Swift work with C language types and features, we can pick the chapter Interacting with C APIs. You may use the following primitive type mapping and pointer mapping when interoperating Swift and C.
Implementing 1D/2D Barcode Reader with Swift and C on Mac
Open Xcode, and then press Command+Shift+N to create a new project. I will demo both command line tool and Cocoa application.
After initializing the new project, we need to import all relevant header files and libraries to the project. The quickest way is to use your mouse to drag header files and libraries to your project. Xcode will automatically add them to project configuration files.
Press Command+N to add a C file, which will be used to invoke Dynamsoft Barcode dylib.
Once it’s done, you will see an alert dialog as follows:
If press Yes, Xcode will automatically generate an Objective-C bridging header file. I had created native_lib.c and native_lib.h, so I opened the bridging header file and add the following line:
#import "native_lib.h"
Referring to the online sample of Dynamsoft Barcode SDK, we can make a few changes:
#include "native_lib.h" int dbr_release_memory(pBarcodeResultArray paryResult) { DBR_FreeBarcodeResults(&paryResult); printf("Game Over\n"); return 0; } pBarcodeResultArray dbr_decodeBarcodeFile(char* pszImageFile) { // Parse command __int64 llFormat = (OneD |QR_CODE); int iMaxCount = 0x7FFFFFFF; int iIndex = 0; ReaderOptions ro = {0}; pBarcodeResultArray paryResult = NULL; int iRet = -1; char * pszTemp = NULL; char * pszTemp1 = NULL; struct timeval begin, end; if (NULL == pszImageFile) { printf("The syntax of the command is incorrect.\n"); return NULL; } // Set license DBR_InitLicense("A825E753D10C6CAC7C661140EC5ABEC3"); // Read barcode gettimeofday(&begin, NULL); ro.llBarcodeFormat = llFormat; ro.iMaxBarcodesNumPerPage = iMaxCount; iRet = DBR_DecodeFile(pszImageFile, &ro, &paryResult); gettimeofday(&end, NULL); // Output barcode result pszTemp = (char*)malloc(4096); if (iRet != DBR_OK) { sprintf(pszTemp, "Failed to read barcode: %s\r\n", DBR_GetErrorString(iRet)); printf("%s", pszTemp); free(pszTemp); return NULL; } if (paryResult->iBarcodeCount == 0) { sprintf(pszTemp, "No barcode found. Total time spent: %.3f seconds.\r\n", ((float)((end.tv_sec * 1000 * 1000 + end.tv_usec) - (begin.tv_sec * 1000 * 1000 + begin.tv_usec))/(1000 * 1000))); printf("%s", pszTemp); DBR_FreeBarcodeResults(&paryResult); return 0; } sprintf(pszTemp, "Total barcode(s) found: %d. Total time spent: %.3f seconds\r\n\r\n", paryResult->iBarcodeCount, ((float)((end.tv_sec * 1000 * 1000 + end.tv_usec) - (begin.tv_sec * 1000 * 1000 + begin.tv_usec))/(1000 * 1000))); printf("%s", pszTemp); return paryResult; }
As for the corresponding header file, add the following code:
#ifndef __DBRConsole__native_lib__ #define __DBRConsole__native_lib__ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/time.h> #include "If_DBR.h" pBarcodeResultArray dbr_decodeBarcodeFile(char* fileName); int dbr_release_memory(pBarcodeResultArray paryResult); const char * GetFormatStr(__int64 format); #endif /* defined(__DBRConsole__native_lib__) */
Command Line Tool in Swift
When writing Swift code, I need to convert Swift String to Char* in order to pass image path.
var file: String = "/Applications/Dynamsoft/Barcode Reader 3.0 Trial/Images/AllSupportedBarcodeTypes.tif" // barcode file //let namePtr = strdup(filePath.bridgeToObjectiveC().UTF8String) var filePtr = strdup(file.cStringUsingEncoding(NSUTF8StringEncoding)!) var fileName: UnsafeMutablePointer<CChar> = UnsafeMutablePointer(filePtr)
I found someone answered how to convert NSString to Char* with the commented line on StackOverflow. The method bridgeToObjectiveC was supported in earlier Xcode 6.x but has been removed in Xcode 6.4.
Get the barcode results:
var result : pBarcodeResultArray = dbr_decodeBarcodeFile(fileName) free(filePtr) println("Total barcode: \(String(result.move().iBarcodeCount))\n.......") var count = result.move().iBarcodeCount var pBarcodeResult: pBarcodeResult = nil var barcodeIndex = 1 // print barcode recognition results for i in 0..<Int(count) { pBarcodeResult = result.move().ppBarcodes.advancedBy(i).move() println("Barcode: \(barcodeIndex++)") println("Page: \(String(pBarcodeResult.move().iPageNum))") var lFormat: __int64 = pBarcodeResult.move().llFormat var format = String.fromCString(GetFormatStr(lFormat)) println("Type: \(format!)") println("Value: \(String.fromCString(pBarcodeResult.move().pBarcodeData)!)") println(".......") } // free C memory dbr_release_memory(result)
Cocoa Application in Swift
Create buttons and text fields in AppDelegate.swift:
@IBOutlet weak var window: NSWindow! @IBOutlet weak var btLoad: NSButton! @IBOutlet weak var btRead: NSButton! @IBOutlet weak var text: NSTextField! @IBOutlet weak var filePath: NSTextField!
Create a function to receive click event:
@IBAction func onClick(sender: NSButton) { var title = sender.title switch(title) { case "Load Barcode File": dispatch_async(dispatch_get_main_queue()) { self.openPanel() } break case "Read Barcode": if self.filePath.stringValue == "" { text.stringValue = "Please add image path!" return } println("default:" + self.filePath.stringValue) var dbr = DBR() text.stringValue = dbr.decodeFile(self.filePath.stringValue)! break default: break } }
In interface builder, connect outlets to UI elements.
Using NSOpenPanel to load files:
func openPanel() { var openPanel = NSOpenPanel() openPanel.allowsMultipleSelection = false openPanel.canChooseDirectories = false openPanel.canCreateDirectories = false openPanel.canChooseFiles = true openPanel.beginWithCompletionHandler { (result) -> Void in if result == NSFileHandlingPanelOKButton { if let path = openPanel.URL?.path { self.filePath.stringValue = path } } } }
Create DBR.swift for reading barcode images:
import Foundation class DBR { func decodeFile(file: String)->String? { var filePtr = strdup(file.cStringUsingEncoding(NSUTF8StringEncoding)!) var fileName: UnsafeMutablePointer<CChar> = UnsafeMutablePointer(filePtr) var result : pBarcodeResultArray = dbr_decodeBarcodeFile(fileName) free(filePtr) println("Total barcode: \(String(result.move().iBarcodeCount))\n.......") var count = result.move().iBarcodeCount var barcodeIndex = 1 var results: String = "" // print barcode recognition results for i in 0..<Int(count) { var pBarcodeResult = result.move().ppBarcodes.advancedBy(i).move() results += "Barcode: \(barcodeIndex++)\n" results += "Page: \(String(pBarcodeResult.move().iPageNum))\n" var lFormat: __int64 = pBarcodeResult.move().llFormat var format = String.fromCString(GetFormatStr(lFormat)) results += "Type: \(format!)\n" results += "Value: \(String.fromCString(pBarcodeResult.move().pBarcodeData)!)\n" results += ".......\n" } // free C memory dbr_release_memory(result) return results } }
Run the 1D/2D barcode reader with GUI:
Source Code
https://github.com/yushulx/swift-barcode-reader