From d06f0ac5e68228429a12d64904fccb21000a25f1 Mon Sep 17 00:00:00 2001 From: Aziz FADIL Date: Sun, 15 Mar 2026 16:03:27 -0400 Subject: [PATCH 1/3] fix(barcode-scanner): delay destroy() after resolve() in windowed mode on iOS When scanning in windowed mode (`windowed: true`) on iOS, the `metadataOutput` delegate calls `invoke?.resolve(jsObject)` followed immediately by `destroy()`. The `destroy()` method restores `webView.isOpaque = true` and resets `webView.backgroundColor`, which interferes with the pending IPC callback delivery. As a result, the JavaScript promise returned by `scan()` never resolves, even though the native side successfully detects and returns the barcode content. This commit delays the `destroy()` call by 300ms using `DispatchQueue.main.asyncAfter`, giving the IPC response enough time to reach the JavaScript layer before the webview properties are restored. Fixes windowed mode scanning on iOS where scan() promise hangs indefinitely after a successful barcode detection. --- .../ios/Sources/BarcodeScannerPlugin.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/barcode-scanner/ios/Sources/BarcodeScannerPlugin.swift b/plugins/barcode-scanner/ios/Sources/BarcodeScannerPlugin.swift index 545eb9f4d4..077f979ece 100644 --- a/plugins/barcode-scanner/ios/Sources/BarcodeScannerPlugin.swift +++ b/plugins/barcode-scanner/ios/Sources/BarcodeScannerPlugin.swift @@ -123,7 +123,13 @@ class BarcodeScannerPlugin: Plugin, AVCaptureMetadataOutputObjectsDelegate { } invoke?.resolve(jsObject) - destroy() + // Delay destroy so the IPC response reaches the JS layer before + // the webview properties (isOpaque, backgroundColor) are restored. + // Without this, the promise returned by scan() never resolves in + // windowed mode on iOS. + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in + self?.destroy() + } } } From 84dd8ecde861e290f1f71dc58f3a0e481c2bca41 Mon Sep 17 00:00:00 2001 From: Aziz FADIL Date: Sun, 15 Mar 2026 16:43:38 -0400 Subject: [PATCH 2/3] fix: guard against duplicate resolve during destroy delay Set isScanning = false immediately after resolve() to prevent metadataOutput from firing multiple times during the 300ms window before destroy() runs. --- plugins/barcode-scanner/ios/Sources/BarcodeScannerPlugin.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/barcode-scanner/ios/Sources/BarcodeScannerPlugin.swift b/plugins/barcode-scanner/ios/Sources/BarcodeScannerPlugin.swift index 077f979ece..a78afe7197 100644 --- a/plugins/barcode-scanner/ios/Sources/BarcodeScannerPlugin.swift +++ b/plugins/barcode-scanner/ios/Sources/BarcodeScannerPlugin.swift @@ -123,6 +123,9 @@ class BarcodeScannerPlugin: Plugin, AVCaptureMetadataOutputObjectsDelegate { } invoke?.resolve(jsObject) + // Stop processing further detections immediately to prevent + // duplicate resolve calls during the delay window below. + self.isScanning = false // Delay destroy so the IPC response reaches the JS layer before // the webview properties (isOpaque, backgroundColor) are restored. // Without this, the promise returned by scan() never resolves in From 044e2c35927572ce4deddb9582be7cfdc1b5964e Mon Sep 17 00:00:00 2001 From: Aziz FADIL Date: Sun, 15 Mar 2026 17:17:06 -0400 Subject: [PATCH 3/3] fix(barcode-scanner): resolve scan() after cleanup in windowed mode on iOS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reorder operations in metadataOutput so that dismantleCamera() and webview restoration run before invoke.resolve(). Previously, resolve() was called first and destroy() immediately after, which restored webView.isOpaque before the IPC response reached JavaScript — causing the scan() promise to never settle in windowed mode. --- .../ios/Sources/BarcodeScannerPlugin.swift | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/plugins/barcode-scanner/ios/Sources/BarcodeScannerPlugin.swift b/plugins/barcode-scanner/ios/Sources/BarcodeScannerPlugin.swift index a78afe7197..572f984dff 100644 --- a/plugins/barcode-scanner/ios/Sources/BarcodeScannerPlugin.swift +++ b/plugins/barcode-scanner/ios/Sources/BarcodeScannerPlugin.swift @@ -122,17 +122,19 @@ class BarcodeScannerPlugin: Plugin, AVCaptureMetadataOutputObjectsDelegate { jsObject["content"] = found.stringValue } - invoke?.resolve(jsObject) - // Stop processing further detections immediately to prevent - // duplicate resolve calls during the delay window below. + // Stop scanning and clean up camera and webview state before + // resolving. In windowed mode, restoring webView.isOpaque after + // resolve() prevents the IPC response from reaching JavaScript. self.isScanning = false - // Delay destroy so the IPC response reaches the JS layer before - // the webview properties (isOpaque, backgroundColor) are restored. - // Without this, the promise returned by scan() never resolves in - // windowed mode on iOS. - DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in - self?.destroy() + dismantleCamera() + if windowed { + let backgroundColor = previousBackgroundColor ?? UIColor.white + webView.isOpaque = true + webView.backgroundColor = backgroundColor + webView.scrollView.backgroundColor = backgroundColor } + invoke?.resolve(jsObject) + invoke = nil } }