From 19bc65a88befd6dbc80cba6e712ccb4a2f3c1f8c Mon Sep 17 00:00:00 2001 From: Tyler Williams Date: Thu, 18 Dec 2025 09:39:56 -0500 Subject: [PATCH 1/3] chore: clean up starter code in expo module --- .../ios/RNMLKitBarcodeScanningModule.swift | 41 +------------------ 1 file changed, 2 insertions(+), 39 deletions(-) diff --git a/modules/react-native-mlkit-barcode-scanning/ios/RNMLKitBarcodeScanningModule.swift b/modules/react-native-mlkit-barcode-scanning/ios/RNMLKitBarcodeScanningModule.swift index 2d756485..16b9e9b3 100644 --- a/modules/react-native-mlkit-barcode-scanning/ios/RNMLKitBarcodeScanningModule.swift +++ b/modules/react-native-mlkit-barcode-scanning/ios/RNMLKitBarcodeScanningModule.swift @@ -1,48 +1,11 @@ import ExpoModulesCore public class RNMLKitBarcodeScanningModule: Module { - // Each module class must implement the definition function. The definition consists of components - // that describes the module's functionality and behavior. - // See https://docs.expo.dev/modules/module-api for more details about available components. public func definition() -> ModuleDefinition { - // Sets the name of the module that JavaScript code will use to refer to the module. Takes a string as an argument. - // Can be inferred from module's class name, but it's recommended to set it explicitly for clarity. - // The module will be accessible from `requireNativeModule('RNMLKitBarcodeScanning')` in JavaScript. Name("RNMLKitBarcodeScanning") - // Defines constant property on the module. - Constant("PI") { - Double.pi - } - - // Defines event names that the module can send to JavaScript. - Events("onChange") - - // Defines a JavaScript synchronous function that runs the native code on the JavaScript thread. - Function("hello") { - return "Hello world! 👋" - } - - // Defines a JavaScript function that always returns a Promise and whose native code - // is by default dispatched on the different thread than the JavaScript runtime runs on. - AsyncFunction("setValueAsync") { (value: String) in - // Send an event to JavaScript. - self.sendEvent("onChange", [ - "value": value - ]) - } - - // Enables the module to be used as a native view. Definition components that are accepted as part of the - // view definition: Prop, Events. - View(RNMLKitBarcodeScanningView.self) { - // Defines a setter for the `url` prop. - Prop("url") { (view: RNMLKitBarcodeScanningView, url: URL) in - if view.webView.url != url { - view.webView.load(URLRequest(url: url)) - } - } - - Events("onLoad") + AsyncFunction("process") { (value: String) in + print(value) } } } From 03f7fe4b445751c856bcfbddf176fe1bf5b34895 Mon Sep 17 00:00:00 2001 From: Tyler Williams Date: Thu, 18 Dec 2025 10:13:21 -0500 Subject: [PATCH 2/3] feat: working, with claude-generated swift types --- .../react-native-mlkit-barcode-scanning/ios/@ | 1 + .../ios/RNMLKitBarcodeScannerResult.swift | 514 ++++++++++++++++++ .../ios/RNMLKitBarcodeScanningModule.swift | 53 +- 3 files changed, 563 insertions(+), 5 deletions(-) create mode 100644 modules/react-native-mlkit-barcode-scanning/ios/@ create mode 100644 modules/react-native-mlkit-barcode-scanning/ios/RNMLKitBarcodeScannerResult.swift diff --git a/modules/react-native-mlkit-barcode-scanning/ios/@ b/modules/react-native-mlkit-barcode-scanning/ios/@ new file mode 100644 index 00000000..daea37b9 --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/ios/@ @@ -0,0 +1 @@ +modules/react-native-mlkit-barcode-scanning/ios/RNMLKitBarcodeScannerResult.swift \ No newline at end of file diff --git a/modules/react-native-mlkit-barcode-scanning/ios/RNMLKitBarcodeScannerResult.swift b/modules/react-native-mlkit-barcode-scanning/ios/RNMLKitBarcodeScannerResult.swift new file mode 100644 index 00000000..c24b0747 --- /dev/null +++ b/modules/react-native-mlkit-barcode-scanning/ios/RNMLKitBarcodeScannerResult.swift @@ -0,0 +1,514 @@ +// +// RNMLKitBarcodeScannerResult.swift +// RNMLKitBarcodeScanning +// + +import ExpoModulesCore +import Foundation +import MLKitBarcodeScanning +import MLKitVision + +// MARK: - Main Result + +struct RNMLKitBarcodeScannerResultRecord: Record { + @Field + var barcodes: [RNMLKitBarcode] = [] +} + +public class RNMLKitBarcodeScannerResult { + var barcodes: [MLKitBarcodeScanning.Barcode] + + init(barcodes: [MLKitBarcodeScanning.Barcode]) { + self.barcodes = barcodes + } + + var record: RNMLKitBarcodeScannerResultRecord { + let result = RNMLKitBarcodeScannerResultRecord() + result.barcodes = self.barcodes.map { barcode in + RNMLKitBarcode.from(barcode: barcode) + } + return result + } +} + +// MARK: - Barcode + +struct RNMLKitBarcode: Record { + @Field + var frame: RNMLKitRect = .init() + @Field + var rawValue: String? + @Field + var displayValue: String? + @Field + var format: String = "" + @Field + var cornerPoints: [RNMLKitPoint] = [] + @Field + var valueType: String = "" + @Field + var email: RNMLKitBarcodeEmail? + @Field + var phone: RNMLKitBarcodePhone? + @Field + var sms: RNMLKitBarcodeSMS? + @Field + var url: RNMLKitBarcodeURLBookmark? + @Field + var wifi: RNMLKitBarcodeWifi? + @Field + var geoPoint: RNMLKitBarcodeGeoPoint? + @Field + var contactInfo: RNMLKitBarcodeContactInfo? + @Field + var calendarEvent: RNMLKitBarcodeCalendarEvent? + @Field + var driverLicense: RNMLKitBarcodeDriverLicense? + + static func from(barcode: MLKitBarcodeScanning.Barcode) -> RNMLKitBarcode { + var result = RNMLKitBarcode() + + result.frame = RNMLKitRect.fromCGRect(rect: barcode.frame) + result.rawValue = barcode.rawValue + result.displayValue = barcode.displayValue + result.format = formatToString(barcode.format) + result.cornerPoints = barcode.cornerPoints?.map { point in + RNMLKitPoint.fromNSValue(point: point) + } ?? [] + result.valueType = valueTypeToString(barcode.valueType) + + // Convert nested objects if present + if let email = barcode.email { + result.email = RNMLKitBarcodeEmail.from(email: email) + } + + if let phone = barcode.phone { + result.phone = RNMLKitBarcodePhone.from(phone: phone) + } + + if let sms = barcode.sms { + result.sms = RNMLKitBarcodeSMS.from(sms: sms) + } + + if let urlBookmark = barcode.url { + result.url = RNMLKitBarcodeURLBookmark.from(url: urlBookmark) + } + + if let wifi = barcode.wifi { + result.wifi = RNMLKitBarcodeWifi.from(wifi: wifi) + } + + if let geoPoint = barcode.geoPoint { + result.geoPoint = RNMLKitBarcodeGeoPoint.from(geoPoint: geoPoint) + } + + if let contactInfo = barcode.contactInfo { + result.contactInfo = RNMLKitBarcodeContactInfo.from(contactInfo: contactInfo) + } + + if let calendarEvent = barcode.calendarEvent { + result.calendarEvent = RNMLKitBarcodeCalendarEvent.from(calendarEvent: calendarEvent) + } + + if let driverLicense = barcode.driverLicense { + result.driverLicense = RNMLKitBarcodeDriverLicense.from(driverLicense: driverLicense) + } + + return result + } + + static func formatToString(_ format: BarcodeFormat) -> String { + if format.contains(.code128) { return "CODE_128" } + if format.contains(.code39) { return "CODE_39" } + if format.contains(.code93) { return "CODE_93" } + if format.contains(.codaBar) { return "CODABAR" } + if format.contains(.dataMatrix) { return "DATA_MATRIX" } + if format.contains(.EAN13) { return "EAN_13" } + if format.contains(.EAN8) { return "EAN_8" } + if format.contains(.ITF) { return "ITF" } + if format.contains(.qrCode) { return "QR_CODE" } + if format.contains(.UPCA) { return "UPC_A" } + if format.contains(.UPCE) { return "UPC_E" } + if format.contains(.PDF417) { return "PDF417" } + if format.contains(.aztec) { return "AZTEC" } + return "UNKNOWN" + } + + static func valueTypeToString(_ valueType: BarcodeValueType) -> String { + switch valueType { + case .contactInfo: return "CONTACT_INFO" + case .email: return "EMAIL" + case .ISBN: return "ISBN" + case .phone: return "PHONE" + case .product: return "PRODUCT" + case .SMS: return "SMS" + case .text: return "TEXT" + case .URL: return "URL" + case .wiFi: return "WIFI" + case .calendarEvent: return "CALENDAR_EVENT" + case .driversLicense: return "DRIVER_LICENSE" + default: return "UNKNOWN" + } + } +} + +// MARK: - Email + +struct RNMLKitBarcodeEmail: Record { + @Field + var address: String? + @Field + var body: String? + @Field + var subject: String? + @Field + var type: String = "UNKNOWN" + + static func from(email: BarcodeEmail) -> RNMLKitBarcodeEmail { + let result = RNMLKitBarcodeEmail() + result.address = email.address + result.body = email.body + result.subject = email.subject + result.type = emailTypeToString(email.type) + return result + } + + static func emailTypeToString(_ type: BarcodeEmailType) -> String { + switch type { + case .work: return "WORK" + case .home: return "HOME" + default: return "UNKNOWN" + } + } +} + +// MARK: - Phone + +struct RNMLKitBarcodePhone: Record { + @Field + var number: String? + @Field + var type: String = "UNKNOWN" + + static func from(phone: BarcodePhone) -> RNMLKitBarcodePhone { + let result = RNMLKitBarcodePhone() + result.number = phone.number + result.type = phoneTypeToString(phone.type) + return result + } + + static func phoneTypeToString(_ type: BarcodePhoneType) -> String { + switch type { + case .work: return "WORK" + case .home: return "HOME" + case .fax: return "FAX" + case .mobile: return "MOBILE" + default: return "UNKNOWN" + } + } +} + +// MARK: - SMS + +struct RNMLKitBarcodeSMS: Record { + @Field + var message: String? + @Field + var phoneNumber: String? + + static func from(sms: BarcodeSMS) -> RNMLKitBarcodeSMS { + let result = RNMLKitBarcodeSMS() + result.message = sms.message + result.phoneNumber = sms.phoneNumber + return result + } +} + +// MARK: - URL Bookmark + +struct RNMLKitBarcodeURLBookmark: Record { + @Field + var title: String? + @Field + var url: String? + + static func from(url: BarcodeURLBookmark) -> RNMLKitBarcodeURLBookmark { + let result = RNMLKitBarcodeURLBookmark() + result.title = url.title + result.url = url.url + return result + } +} + +// MARK: - WiFi + +struct RNMLKitBarcodeWifi: Record { + @Field + var ssid: String? + @Field + var password: String? + @Field + var encryptionType: String = "OPEN" + + static func from(wifi: BarcodeWifi) -> RNMLKitBarcodeWifi { + let result = RNMLKitBarcodeWifi() + result.ssid = wifi.ssid + result.password = wifi.password + result.encryptionType = wifiEncryptionTypeToString(wifi.type) + return result + } + + static func wifiEncryptionTypeToString(_ type: BarcodeWiFiEncryptionType) -> String { + switch type { + case .open: return "OPEN" + case .WPA: return "WPA" + case .WEP: return "WEP" + default: return "OPEN" + } + } +} + +// MARK: - Geo Point + +struct RNMLKitBarcodeGeoPoint: Record { + @Field + var latitude: Double = 0.0 + @Field + var longitude: Double = 0.0 + + static func from(geoPoint: BarcodeGeoPoint) -> RNMLKitBarcodeGeoPoint { + let result = RNMLKitBarcodeGeoPoint() + result.latitude = geoPoint.latitude + result.longitude = geoPoint.longitude + return result + } +} + +// MARK: - Address + +struct RNMLKitBarcodeAddress: Record { + @Field + var addressLines: [String] = [] + @Field + var type: String = "UNKNOWN" + + static func from(address: BarcodeAddress) -> RNMLKitBarcodeAddress { + let result = RNMLKitBarcodeAddress() + result.addressLines = address.addressLines ?? [] + result.type = addressTypeToString(address.type) + return result + } + + static func addressTypeToString(_ type: BarcodeAddressType) -> String { + switch type { + case .work: return "WORK" + case .home: return "HOME" + default: return "UNKNOWN" + } + } +} + +// MARK: - Person Name + +struct RNMLKitBarcodePersonName: Record { + @Field + var formattedName: String? + @Field + var first: String? + @Field + var last: String? + @Field + var middle: String? + @Field + var prefix: String? + @Field + var pronunciation: String? + @Field + var suffix: String? + + static func from(name: BarcodePersonName) -> RNMLKitBarcodePersonName { + let result = RNMLKitBarcodePersonName() + result.formattedName = name.formattedName + result.first = name.first + result.last = name.last + result.middle = name.middle + result.prefix = name.prefix + result.pronunciation = name.pronunciation + result.suffix = name.suffix + return result + } +} + +// MARK: - Contact Info + +struct RNMLKitBarcodeContactInfo: Record { + @Field + var addresses: [RNMLKitBarcodeAddress]? + @Field + var emails: [RNMLKitBarcodeEmail]? + @Field + var name: RNMLKitBarcodePersonName? + @Field + var phones: [RNMLKitBarcodePhone]? + @Field + var urls: [String]? + @Field + var jobTitle: String? + @Field + var organization: String? + + static func from(contactInfo: BarcodeContactInfo) -> RNMLKitBarcodeContactInfo { + var result = RNMLKitBarcodeContactInfo() + + if let addresses = contactInfo.addresses { + result.addresses = addresses.map { RNMLKitBarcodeAddress.from(address: $0) } + } + + if let emails = contactInfo.emails { + result.emails = emails.map { RNMLKitBarcodeEmail.from(email: $0) } + } + + if let name = contactInfo.name { + result.name = RNMLKitBarcodePersonName.from(name: name) + } + + if let phones = contactInfo.phones { + result.phones = phones.map { RNMLKitBarcodePhone.from(phone: $0) } + } + + result.urls = contactInfo.urls + result.jobTitle = contactInfo.jobTitle + result.organization = contactInfo.organization + + return result + } +} + +// MARK: - Calendar Event + +struct RNMLKitBarcodeCalendarEvent: Record { + @Field + var eventDescription: String? + @Field + var location: String? + @Field + var organizer: String? + @Field + var status: String? + @Field + var summary: String? + @Field + var start: String? + @Field + var end: String? + + static func from(calendarEvent: BarcodeCalendarEvent) -> RNMLKitBarcodeCalendarEvent { + let result = RNMLKitBarcodeCalendarEvent() + result.eventDescription = calendarEvent.eventDescription + result.location = calendarEvent.location + result.organizer = calendarEvent.organizer + result.status = calendarEvent.status + result.summary = calendarEvent.summary + + // Convert dates to ISO8601 strings if present + if let start = calendarEvent.start { + result.start = ISO8601DateFormatter().string(from: start) + } + if let end = calendarEvent.end { + result.end = ISO8601DateFormatter().string(from: end) + } + + return result + } +} + +// MARK: - Driver License + +struct RNMLKitBarcodeDriverLicense: Record { + @Field + var addressCity: String? + @Field + var addressState: String? + @Field + var addressStreet: String? + @Field + var addressZip: String? + @Field + var birthDate: String? + @Field + var documentType: String? + @Field + var expiryDate: String? + @Field + var firstName: String? + @Field + var gender: String? + @Field + var issuingCountry: String? + @Field + var lastName: String? + @Field + var licenseNumber: String? + @Field + var middleName: String? + + static func from(driverLicense: BarcodeDriverLicense) -> RNMLKitBarcodeDriverLicense { + var result = RNMLKitBarcodeDriverLicense() + result.addressCity = driverLicense.addressCity + result.addressState = driverLicense.addressState + result.addressStreet = driverLicense.addressStreet + result.addressZip = driverLicense.addressZip + result.birthDate = driverLicense.birthDate + result.documentType = driverLicense.documentType + result.expiryDate = driverLicense.expiryDate + result.firstName = driverLicense.firstName + result.gender = driverLicense.gender + result.issuingCountry = driverLicense.issuingCountry + result.lastName = driverLicense.lastName + result.licenseNumber = driverLicense.licenseNumber + result.middleName = driverLicense.middleName + + return result + } +} + +// MARK: - Helper Types from Core + +struct RNMLKitPoint: Record { + @Field + var x: CGFloat = 0 + @Field + var y: CGFloat = 0 + + static func fromNSValue(point: NSValue) -> RNMLKitPoint { + let cgPoint = point.cgPointValue + let result = RNMLKitPoint() + result.x = cgPoint.x + result.y = cgPoint.y + return result + } +} + +struct RNMLKitRect: Record { + @Field + var origin: RNMLKitPoint = RNMLKitPoint() + @Field + var size: RNMLKitPoint = RNMLKitPoint() + + static func fromCGRect(rect: CGRect) -> RNMLKitRect { + var result = RNMLKitRect() + + let origin = RNMLKitPoint() + origin.x = rect.origin.x + origin.y = rect.origin.y + + let size = RNMLKitPoint() + size.x = rect.width + size.y = rect.height + + result.origin = origin + result.size = size + + return result + } +} diff --git a/modules/react-native-mlkit-barcode-scanning/ios/RNMLKitBarcodeScanningModule.swift b/modules/react-native-mlkit-barcode-scanning/ios/RNMLKitBarcodeScanningModule.swift index 16b9e9b3..d1de1b6a 100644 --- a/modules/react-native-mlkit-barcode-scanning/ios/RNMLKitBarcodeScanningModule.swift +++ b/modules/react-native-mlkit-barcode-scanning/ios/RNMLKitBarcodeScanningModule.swift @@ -1,11 +1,54 @@ import ExpoModulesCore +import MLKitBarcodeScanning +import RNMLKitCore + +// TODO: move this to core? +// Function to reject a promise with a specified message and domain +func rejectPromiseWithMessage(promise: Promise, message: String, domain: String) +{ + promise.reject( + NSError( + domain: domain, code: 1, + userInfo: [NSLocalizedDescriptionKey: message]) + ) +} + +let ERROR_DOMAIN: String = + "red.infinite.reactnativemlkit.BarcodeScanningErrorDomain" public class RNMLKitBarcodeScanningModule: Module { - public func definition() -> ModuleDefinition { - Name("RNMLKitBarcodeScanning") + // TODO: set up ways to initialize and change this + var barcodeScanner: BarcodeScanner? = BarcodeScanner.barcodeScanner() + + public func definition() -> ModuleDefinition { + Name("RNMLKitBarcodeScanning") + + AsyncFunction("process") { (imagePath: String, promise: Promise) in + let logger = Logger(logHandlers: [ + createOSLogHandler(category: Logger.EXPO_LOG_CATEGORY) + ]) + + Task { + do { + guard let barcodeScanner = self.barcodeScanner else { + rejectPromiseWithMessage( + promise: promise, + message: + "[RNMLKitBarcodeScanning.process] Barcode Scanner not initiliazed", + domain: ERROR_DOMAIN) + return + } + let image = try RNMLKitImage(imagePath: imagePath) + let barcodes = try await barcodeScanner.process(image.visionImage) + + logger.debug(barcodes) - AsyncFunction("process") { (value: String) in - print(value) + let result = RNMLKitBarcodeScannerResult(barcodes: barcodes) + promise.resolve(result.record) + } catch { + rejectPromiseWithMessage(promise: promise, message: "[RNMLKitBarcodeScanning.process] Error processing barcode: \(error)", domain: ERROR_DOMAIN) + } + } + } } - } } From 461a248c8eca54e448d9902db1f90a8a9df5422c Mon Sep 17 00:00:00 2001 From: Tyler Williams Date: Thu, 15 Jan 2026 09:38:53 -0500 Subject: [PATCH 3/3] fix: implement initialize method --- .../ios/RNMLKitBarcodeScanningModule.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/react-native-mlkit-barcode-scanning/ios/RNMLKitBarcodeScanningModule.swift b/modules/react-native-mlkit-barcode-scanning/ios/RNMLKitBarcodeScanningModule.swift index d1de1b6a..02523e5a 100644 --- a/modules/react-native-mlkit-barcode-scanning/ios/RNMLKitBarcodeScanningModule.swift +++ b/modules/react-native-mlkit-barcode-scanning/ios/RNMLKitBarcodeScanningModule.swift @@ -17,12 +17,16 @@ let ERROR_DOMAIN: String = "red.infinite.reactnativemlkit.BarcodeScanningErrorDomain" public class RNMLKitBarcodeScanningModule: Module { - // TODO: set up ways to initialize and change this - var barcodeScanner: BarcodeScanner? = BarcodeScanner.barcodeScanner() + var barcodeScanner: BarcodeScanner? = nil public func definition() -> ModuleDefinition { Name("RNMLKitBarcodeScanning") + AsyncFunction("initialize") { (promise: Promise) in + self.barcodeScanner = BarcodeScanner.barcodeScanner() + promise.resolve(nil) + } + AsyncFunction("process") { (imagePath: String, promise: Promise) in let logger = Logger(logHandlers: [ createOSLogHandler(category: Logger.EXPO_LOG_CATEGORY)