DEV Community

Xiao Ling
Xiao Ling

Posted on • Originally published at dynamsoft.com

2 1

How to Implement a Flutter QR Code Scanner Plugin for iOS in Swift

Previously, I wrote a blog post sharing how to implement a Flutter QR code scanner plugin for Android. For cross-platform mobile development, a plugin is not perfect if it does not support both Android and iOS. In this article, I am going to complement the iOS part of the plugin in order to bring the best development experience for mobile developers.

Flutter QR Code Scanner Plugin

https://pub.dev/packages/flutter_camera_qrcode_scanner

Dev Environment

  • M1 Mac
  • Xcode 13.2.1

Step-by-step Guide: Implement Flutter QR Code Scanner Plugin for iOS

Step 1: Get the Existing Flutter Plugin Project:

git clone https://github.com/yushulx/flutter_qrcode_scanner
Enter fullscreen mode Exit fullscreen mode

Step 2: Add Support for iOS

cd flutter_qrcode_scanner
flutter create --template=plugin --platforms=ios .
Enter fullscreen mode Exit fullscreen mode

Step 3: Install Dynamsoft Camera Enhancer and Dynamsoft Barcode Reader

Open iOS/flutter_camera_qrcode_scanner.podspec to add Dynamsoft Camera Enhancer 2.1.1 and Dynamsoft Barcode Reader 8.9.1:

s.dependency 'DynamsoftBarcodeReader', '8.9.1'
s.dependency 'DynamsoftCameraEnhancer', '2.1.1'
Enter fullscreen mode Exit fullscreen mode

The dependencies will be installed via pod install.

Activating Mobile QR Code SDK

Click here to get a valid license key for Dynamsoft Barcode Reader.

Step 4: Implement the Factory and the Platform View Using Swift Code

Since we have completed the code logic on the Dart side, we only need to focus on the platform-related code. According to the official tutorial and the Android part of the plugin, we can write Swift code for iOS as follows:

  1. Based on the structure of the Android QRCodeScanner class, we create a FLQRCodeScanner class in iOS/Classes/FLQRCodeScanner.swift:

    import Flutter
    import UIKit
    import DynamsoftBarcodeReader
    import DynamsoftCameraEnhancer
    
    public protocol DetectionHandler {
            func onDetected(data: NSArray)
        }
    
    class FLQRCodeScanner: NSObject, DBRTextResultDelegate {
    
        private var cameraView: DCECameraView
        private var dce: DynamsoftCameraEnhancer
        private var barcodeReader: DynamsoftBarcodeReader! = nil
        private var handler: DetectionHandler?
    
        init(cameraView: DCECameraView, dce: DynamsoftCameraEnhancer) {
            self.cameraView = cameraView
            self.cameraView.overlayVisible = true
            self.dce = dce
            super.init()
    
            createBarcodeReader(dce: dce)
        }
    
        func setDetectionHandler(handler: DetectionHandler) {
            self.handler = handler;
        }
    
        func createBarcodeReader(dce: DynamsoftCameraEnhancer) {
            // To activate the sdk, apply for a license key: https://www.dynamsoft.com/customer/license/trialLicense?product=dbr
            barcodeReader = DynamsoftBarcodeReader.init(license: "license-key")
            barcodeReader.setCameraEnhancer(dce)
    
            // Set text result call back to get barcode results.
            barcodeReader.setDBRTextResultDelegate(self, userData: nil)
    
            // Start the barcode decoding thread.
            barcodeReader.startScanning()
        }
    
        func textResultCallback(_ frameId: Int, results: [iTextResult]?, userData: NSObject?) {
            if results!.count > 0 {
                let outResults = NSMutableArray()
                for item in results! {
                    let subDic = NSMutableDictionary()
                    if item.barcodeFormat_2 != EnumBarcodeFormat2.Null {
                        subDic.setObject(item.barcodeFormatString_2 ?? "", forKey: "format" as NSCopying)
                    }else{
                        subDic.setObject(item.barcodeFormatString ?? "", forKey: "format" as NSCopying)
                    }
                    subDic.setObject(item.barcodeText ?? "", forKey: "text" as NSCopying)
                    let points = item.localizationResult?.resultPoints as! [CGPoint]
                    subDic.setObject(Int(points[0].x), forKey: "x1" as NSCopying)
                    subDic.setObject(Int(points[0].y), forKey: "y1" as NSCopying)
                    subDic.setObject(Int(points[1].x), forKey: "x2" as NSCopying)
                    subDic.setObject(Int(points[1].y), forKey: "y2" as NSCopying)
                    subDic.setObject(Int(points[2].x), forKey: "x3" as NSCopying)
                    subDic.setObject(Int(points[2].y), forKey: "y3" as NSCopying)
                    subDic.setObject(Int(points[3].x), forKey: "x4" as NSCopying)
                    subDic.setObject(Int(points[3].y), forKey: "y4" as NSCopying)
                    subDic.setObject(item.localizationResult?.angle ?? 0, forKey: "angle" as NSCopying)
                    outResults.add(subDic)
                }
    
                if handler != nil {
                    handler!.onDetected(data: outResults)
                }
            }
        } 
    
        func startScan() {
            cameraView.overlayVisible = true
            barcodeReader.startScanning()
        }
    
        func stopScan() {
            cameraView.overlayVisible = false
            barcodeReader.stopScanning()
        }
    
        func setBarcodeFormats(arg:NSDictionary) {
            let formats:Int = arg.value(forKey: "formats") as! Int
            let settings = try! barcodeReader!.getRuntimeSettings()
            settings.barcodeFormatIds = formats
            barcodeReader!.update(settings, error: nil)
        }
    
        func setLicense(license: String) {
            barcodeReader.license = license
        }
    }
    
  2. Then we create a FLNativeView class in iOS/Classes/FLNativeView.swift to implement the camera view:

    import Flutter
    import UIKit
    import DynamsoftCameraEnhancer
    
    class FLNativeView: NSObject, FlutterPlatformView, DetectionHandler {
        private var _view: UIView
        private var messenger: FlutterBinaryMessenger
        private var channel: FlutterMethodChannel
        private var qrCodeScanner: FLQRCodeScanner
        init(
            frame: CGRect,
            viewIdentifier viewId: Int64,
            arguments args: Any?,
            binaryMessenger: FlutterBinaryMessenger
        ) {
            self.messenger = binaryMessenger
            let cameraView = DCECameraView.init(frame: frame)
            let dce = DynamsoftCameraEnhancer.init(view: cameraView)
            dce.open()
            dce.setFrameRate(30)
            _view = cameraView
    
            qrCodeScanner = FLQRCodeScanner.init(cameraView: cameraView, dce: dce)
    
            channel = FlutterMethodChannel(name: "com.dynamsoft.flutter_camera_qrcode_scanner/nativeview_" + String(viewId), binaryMessenger: messenger)
    
            super.init()
    
            qrCodeScanner.setDetectionHandler(handler: self)
            channel.setMethodCallHandler({
            (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
                switch call.method {
                    case "startScanning":
                        self.qrCodeScanner.startScan()
                        result(.none)
                    case "stopScanning":
                        self.qrCodeScanner.stopScan()
                        result(.none)
                    case "setLicense":
                        self.qrCodeScanner.setLicense(license: (call.arguments as! NSDictionary).value(forKey: "license") as! String)
                        result(.none)
                    case "setBarcodeFormats":
                        self.qrCodeScanner.setBarcodeFormats(arg: call.arguments as! NSDictionary)
                        result(.none)
                    default:
                        result(.none)
                    }
            })
        }
    
        func view() -> UIView {
            return _view
        }
    
        func onDetected(data: NSArray) {
            DispatchQueue.main.async {
                    self.channel.invokeMethod("onDetected", arguments: data)
                }
        }
    }
    
    

    The native view contains a channel to handle Flutter method calls (from Dart to native). It also triggers the onDetected callback function (from native to Dart). When a QR code is detected, the channel will send the QR information from the native side to the Dart side .

  3. Next, create a FLNativeViewFactory class in iOS/Classes/FLNativeViewFactory.swift to initialize the native view:

    import Flutter
    import UIKit
    
    class FLNativeViewFactory: NSObject, FlutterPlatformViewFactory {
        private var messenger: FlutterBinaryMessenger
    
        init(messenger: FlutterBinaryMessenger) {
            self.messenger = messenger
            super.init()
        }
    
        func create(
            withFrame frame: CGRect,
            viewIdentifier viewId: Int64,
            arguments args: Any?
        ) -> FlutterPlatformView {
            return FLNativeView(
                frame: frame,
                viewIdentifier: viewId,
                arguments: args,
                binaryMessenger: messenger
            )
        }
    }
    
  4. The final step is to register the factory in iOS/Classes/SwiftFlutterCameraQrcodeScannerPlugin.swift:

    import Flutter
    import UIKit
    
    public class SwiftFlutterCameraQrcodeScannerPlugin: NSObject, FlutterPlugin {
      public static func register(with registrar: FlutterPluginRegistrar) {  
        let factory = FLNativeViewFactory(messenger: registrar.messenger())
        registrar.register(factory, withId: "com.dynamsoft.flutter_camera_qrcode_scanner/nativeview")
      }
    }
    

So far, the native Swift code for plugin is done.

Step 5: Test the QR code scanner in Flutter

  1. Go to the example folder and add the camera access permission to ios/Runner/Info.plist:

    <key>NSCameraUsageDescription</key>
    <string>Can I use the camera please?</string>
    <key>NSMicrophoneUsageDescription</key>
    <string>Can I use the mic please?</string>
    
  2. Run the example on iOS devices:

    flutter run
    

    Flutter iOS QR code scanner

Source Code

https://github.com/yushulx/flutter_qrcode_scanner

Sentry image

Hands-on debugging session: instrument, monitor, and fix

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

RSVP here →

Top comments (0)

Sentry mobile image

Improving mobile performance, from slow screens to app start time

Based on our experience working with thousands of mobile developer teams, we developed a mobile monitoring maturity curve.

Read more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay