DEV Community

Xiao Ling
Xiao Ling

Posted on • Originally published at dynamsoft.com

Building a macOS Framework with Objective-C++ and C++ for Swift Barcode Detection

Dynamsoft currently provides only a C++ Barcode SDK for macOS. Previously, we built a macOS barcode scanner by combining Swift, Objective-C and C++. Since modern macOS app development primarily relies on Swift, creating a macOS framework that wraps the C++ Barcode SDK with Objective-C++ can streamline the process of calling C++ APIs from Swift. In this article, we will extract the Objective-C++ and C++ code from the macOS barcode scanner project and move them into a dedicated macOS framework project. The original SwiftUI project will then use this macOS framework to handle barcode detection.

macOS SwfitUI Barcode Scanner Demo Video

Prerequisites

Creating a macOS Framework Project

  1. Scaffold a new macOS framework project in Xcode:

    macOS Framework Project

  2. Drag the header and library files from the Dynamsoft Barcode Reader C++ SDK into the project:

    macOS Framework project with .h and .dylib

    Next, navigate to Build Phases > Copy Files and add all the .dylib files to the Frameworks section.

  3. In the public header file (DCV.h), define the required enum types and expose essential Objective-C++ APIs:

    #ifndef DCV_H
    #define DCV_H
    
    #import <Foundation/Foundation.h>
    
    FOUNDATION_EXPORT double DCVVersionNumber;
    
    FOUNDATION_EXPORT const unsigned char DCVVersionString[];
    
    typedef NS_ENUM(NSInteger, PixelFormat) {
        ...
    };
    typedef NS_ENUM(unsigned long long, BarcodeType) {
        ...
    };
    
    @interface CaptureVisionWrapper : NSObject
    + (int)initializeLicense:(NSString *)licenseKey;
    
    - (NSArray *)decodeBufferWithData:(void *)baseAddress
                                width:(int)width
                               height:(int)height
                               stride:(int)stride
                          pixelFormat:(PixelFormat)pixelFormat;
    
    - (NSArray *)decodeFileWithPath:(NSString *)filePath;
    - (NSString *)getSettings;
    - (int)setSettings:(NSString *)jsonString;
    - (int)setBarcodeFormats:(unsigned long long)formats;
    @end
    
    #endif
    
    
  4. Create a dcv.mm file to implement the APIs by calling the underlying C++ SDK methods:

    #import <CoreVideo/CoreVideo.h>
    #import <Foundation/Foundation.h>
    
    #import "DCV.h"
    
    #ifdef __cplusplus
    #include "DynamsoftCaptureVisionRouter.h"
    #include "DynamsoftUtility.h"
    #include "template.h"
    
    using namespace dynamsoft::license;
    using namespace dynamsoft::cvr;
    using namespace dynamsoft::dbr;
    using namespace dynamsoft::utility;
    using namespace dynamsoft::basic_structures;
    
    #endif 
    
    @implementation CaptureVisionWrapper {
        CCaptureVisionRouter *cvr; 
    }
    
    + (int)initializeLicense:(NSString *)licenseKey {
        char errorMsgBuffer[512] = {0}; 
        const char *licenseCString = [licenseKey UTF8String];
        int ret = CLicenseManager::InitLicense(licenseCString, errorMsgBuffer,
                                                sizeof(errorMsgBuffer));
    
        if (ret != 0) {
            NSString *errorMessage = [NSString stringWithUTF8String:errorMsgBuffer];
            NSLog(@"License initialization failed: %@", errorMessage);
        } else {
            NSLog(@"License initialized successfully");
        }
    
        return ret;
    }
    
    - (instancetype)init {
        self = [super init];
        if (self) {
            try {
            cvr = new CCaptureVisionRouter(); // Initialize the C++ object
    
            char errorMsgBuffer[512] = {0};
            int ret = cvr->InitSettings(jsonString.c_str(), errorMsgBuffer,
                                        sizeof(errorMsgBuffer));
    
            if (ret != 0) {
                NSString *errorMessage = [NSString stringWithUTF8String:errorMsgBuffer];
                NSLog(@"Init setting failed: %@", errorMessage);
            }
            } catch (const std::exception &ex) {
            NSLog(@"Exception during initialization: %s", ex.what());
            } catch (...) {
            NSLog(@"Unknown exception during initialization");
            }
        }
        return self;
    }
    
    - (NSArray *)decodeBufferWithData:(void *)baseAddress
                                width:(int)width
                            height:(int)height
                            stride:(int)stride
                        pixelFormat:(PixelFormat)pixelFormat {
        if (!baseAddress) {
            NSLog(@"Error: baseAddress is null");
            return nil;
        }
    
        NSArray *results = nil;
    
        try {
            CImageData *imageStruct = new CImageData(
                stride * height, (unsigned char *)baseAddress, width, height, stride,
                static_cast<ImagePixelFormat>(pixelFormat));
    
            CCapturedResult *result = cvr->Capture(imageStruct, "");
            results = [self wrapResults:result];
            delete imageStruct;
        } catch (const std::exception &ex) {
            NSLog(@"Exception during captureImageWithData: %s", ex.what());
        } catch (...) {
            NSLog(@"Unknown exception during captureImageWithData");
        }
    
        return results;
    }
    
    - (NSArray *)decodeFileWithPath:(NSString *)filePath {
        if (!filePath) {
            NSLog(@"Error: filePath is null");
            return nil;
        }
    
        NSArray *results = nil;
    
        try {
            const char *fileCString = [filePath UTF8String];
            CCapturedResult *result = cvr->Capture(fileCString, "");
            results = [self wrapResults:result];
        } catch (const std::exception &ex) {
            NSLog(@"Exception during captureImageWithFilePath: %s", ex.what());
        } catch (...) {
            NSLog(@"Unknown exception during captureImageWithFilePath");
        }
    
        return results;
    }
    
    - (NSArray *)wrapResults:(CCapturedResult *)result {
        if (!result) {
            NSLog(@"Error: result is null");
            return nil;
        }
    
        NSMutableArray *barcodeArray = [NSMutableArray array];
    
        try {
            CDecodedBarcodesResult *barcodeResult = result->GetDecodedBarcodesResult();
            if (!barcodeResult || barcodeResult->GetItemsCount() == 0) {
            return nil;
            }
    
            int barcodeResultItemCount = barcodeResult->GetItemsCount();
    
            for (int j = 0; j < barcodeResultItemCount; j++) {
            const CBarcodeResultItem *barcodeResultItem = barcodeResult->GetItem(j);
            const char *format = barcodeResultItem->GetFormatString();
            const char *text = barcodeResultItem->GetText();
            int angle = barcodeResultItem->GetAngle();
            CPoint *points = barcodeResultItem->GetLocation().points;
            unsigned char *raw = barcodeResultItem->GetBytes();
    
            NSDictionary *barcodeData = @{
                @"format" : format ? [NSString stringWithUTF8String:format] : @"",
                @"text" : text ? [NSString stringWithUTF8String:text] : @"",
                @"angle" : @(angle),
                @"barcodeBytes" :
                    [NSData dataWithBytes:raw
                                length:barcodeResultItem->GetBytesLength()],
                @"points" : @[
                @{@"x" : @(points[0][0]), @"y" : @(points[0][1])},
                @{@"x" : @(points[1][0]), @"y" : @(points[1][1])},
                @{@"x" : @(points[2][0]), @"y" : @(points[2][1])},
                @{@"x" : @(points[3][0]), @"y" : @(points[3][1])}
                ]
            };
    
            [barcodeArray addObject:barcodeData];
            }
    
            barcodeResult->Release();
            result->Release();
        } catch (const std::exception &ex) {
            NSLog(@"Exception during wrapResults: %s", ex.what());
        } catch (...) {
            NSLog(@"Unknown exception during wrapResults");
        }
    
        return [barcodeArray copy];
    }
    
    - (void)dealloc {
        if (cvr) {
            delete cvr;
            cvr = nullptr;
        }
    }
    
    - (NSString *)getSettings {
        char *tpl = cvr->OutputSettings("");
        NSString *settings = [NSString stringWithUTF8String:tpl];
        dynamsoft::cvr::CCaptureVisionRouter::FreeString(tpl);
        return settings;
    }
    
    - (int)setSettings:(NSString *)json {
        char *tpl = (char *)[json UTF8String];
        char errorMessage[256];
        int ret = cvr->InitSettings(tpl, errorMessage, 256);
        if (ret != 0) {
            NSLog(@"Set settings failed: %s", errorMessage);
        }
        return ret;
    }
    
    - (int)setBarcodeFormats:(unsigned long long)formats {
        SimplifiedCaptureVisionSettings pSettings = {};
        cvr->GetSimplifiedSettings("", &pSettings);
        pSettings.barcodeSettings.barcodeFormatIds = formats;
    
        char szErrorMsgBuffer[256];
        int ret = cvr->UpdateSettings("", &pSettings, szErrorMsgBuffer, 256);
        if (ret != 0) {
            NSLog(@"Set barcode formats failed: %s", szErrorMsgBuffer);
        }
        return ret;
    }
    @end
    
  5. Edit the scheme to use the Release build configuration for the framework:

    macOS Framework Scheme for release build

  6. Build the framework project, then locate the generated DCV.framework by navigating to Product > Show Build Folder in Finder:

    macOS Framework Build Success

Creating a macOS SwiftUI Project and Integrating the macOS Framework

  1. Start a new macOS SwiftUI project in Xcode:

    macOS SwiftUI Project

  2. Copy ImageViewer.swift, ContentView.swift, CaptureVisionApp.swift, CameraViewController.swift, CameraView.swift and CameraView.swift from the original SwiftUI project to the new project.

  3. Disable the App Sandbox capability in the entitlements file.

    <key>com.apple.security.app-sandbox</key>
    <false/>
    
  4. Add DCV.framework to the project by selecting File > Add Files. Ensure the Embed & Sign option is selected in the Frameworks, Libraries, and Embedded Content section.

    macOS SwiftUI Project with DCV.framework

  5. In CameraViewController.swift, modify the code logic to utilize the framework:

    import DCV
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        // Initialize the license here
        // Get a trial license key from: https://www.dynamsoft.com/customer/license/trialLicense/?product=dcv&package=cross-platform
        let licenseKey =
            "LICENSE-KEY"
    
        let result = CaptureVisionWrapper.initializeLicense(licenseKey)
    
        if result == 0 {
            print("License initialized successfully")
        } else {
            print("Failed to initialize license with error code: \(result)")
        }
    
        ...
    }
    
    func processCameraFrame(_ pixelBuffer: CVPixelBuffer) {
        let previewWidth = CVPixelBufferGetWidth(pixelBuffer)
        let previewHeight = CVPixelBufferGetHeight(pixelBuffer)
    
        DispatchQueue.main.async { [weak self] in
            guard let self = self else { return }
            self.overlayView.cameraPreviewSize = CGSize(width: previewWidth, height: previewHeight)
        }
    
        CVPixelBufferLockBaseAddress(pixelBuffer, .readOnly)
    
        let baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer)
        let width = CVPixelBufferGetWidth(pixelBuffer)
        let height = CVPixelBufferGetHeight(pixelBuffer)
        let bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer)
        let pixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer)
    
        if let baseAddress = baseAddress {
            let barcodeArray =
                cv.decodeBuffer(
                    withData: baseAddress, width: Int32(width), height: Int32(Int(height)),
                    stride: Int32(Int(bytesPerRow)), pixelFormat: PixelFormat.ARGB8888)
                as? [[String: Any]] ?? []
    
            DispatchQueue.main.async { [weak self] in
                guard let self = self else { return }
                self.overlayView.barcodeData = barcodeArray
                self.overlayView.setNeedsDisplay(self.overlayView.bounds) 
            }
        }
    
        CVPixelBufferUnlockBaseAddress(pixelBuffer, .readOnly)
    
    }
    

    You need to replace the LICENSE-KEY with your own license key.

  6. Build and run the macOS SwiftUI project to see the barcode scanner in action:

    macos framework for barcode scanning in SwiftUI

Publishing the macOS Framework to CocoaPods

After successfully building the macOS framework, you can publish it to CocoaPods for easy integration into other macOS projects.

  1. Compress the DCV.framework into a .zip file and upload it to a file hosting service, such as GitHub.
  2. Create a CocoaPods podspec file named DCV.podspec:

    Pod::Spec.new do |s|
    s.name             = 'DCV'
    s.version          = '1.0.1'
    s.summary          = 'macOS barcode reading framework.'
    
    s.description      = <<-DESC
    DCV is a versatile framework designed for barcode reading. It is built with Dynamsoft Barcode Reader C++ SDK. The framework supports various barcode types, including Code 39, Code 128, QR Code, DataMatrix, PDF417, etc.
    DESC
    
    s.homepage         = 'https://github.com/yushulx/ios-swiftui-barcode-mrz-document-scanner/tree/main/examples/macos_framework'
    s.license = { :type => 'MIT', :file => File.expand_path('LICENSE') }
    s.author           = { 'yushulx' => 'lingxiao1002@gmail.com' }
    s.source           = { :http => 'https://github.com/yushulx/ios-swiftui-barcode-mrz-document-scanner/raw/refs/heads/main/examples/macos_framework/dist/DCV.framework.zip' }
    
    s.macos.deployment_target = '10.13'
    s.vendored_frameworks = 'DCV.framework'
    
    s.frameworks = ['Foundation']
    s.requires_arc = true
    end
    
    

    Explanation:

    • The source field specifies the URL of the .zip file containing the framework.
    • The vendored_frameworks field specifies the prebuilt frameworks that are bundled with your plugin and should be included in the consuming project.
    • The frameworks field specifies the dependencies of the plugin.
    • The framework and license file should be placed in the same directory as the podspec file.
  3. Validate the podspec file:

    pod lib lint DCV.podspec
    
  4. Register a CocoaPods account if you don’t already have one:

    pod trunk register your_email@example.com 'Your Name'
    
  5. Publish the pod:

    pod trunk push DCV.podspec
    
  6. Run pod search DCV to verify if the pod has been successfully published. If you have received an approval email but the pod is not yet visible, run pod repo add master https://github.com/CocoaPods/Specs to manually adds the CocoaPods "master" spec repository to your local CocoaPods configuration.

Source Code

https://github.com/yushulx/ios-swiftui-barcode-mrz-document-scanner/tree/main/examples/macos_framework

Top comments (0)