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
Step 2: Add Support for iOS
cd flutter_qrcode_scanner
flutter create --template=plugin --platforms=ios .
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'
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:
- Based on the structure of the Android QRCodeScanner class, we create a
FLQRCodeScanner
class iniOS/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()
}
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
- 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
Source Code
Originally published at https://www.dynamsoft.com on March 10, 2022.