Skip to content

[barcode-scanner] iOS: scan() promise never resolves in windowed mode #3348

@afadil

Description

@afadil

Describe the bug

When using the barcode scanner plugin on iOS with windowed: true, the JavaScript promise returned by scan() never resolves — even though the native side successfully detects the barcode and sends the IPC response.

With windowed: false, the same code works perfectly.

Reproduction

import { scan, Format } from '@tauri-apps/plugin-barcode-scanner';

// This never resolves on iOS:
const result = await scan({ windowed: true, formats: [Format.QRCode] });
console.log(result); // Never reached

Evidence

  • The IPC response IS visible in Safari's network inspector — the native side returns {"content": "D56UPN", "format": "QR_CODE"} correctly
  • But the JavaScript await scan() never continues — no code after await executes
  • windowed: false works correctly with identical JavaScript code
  • cancel() works correctly (the reject path is unaffected)

Analysis

Looking at BarcodeScannerPlugin.swift, the metadataOutput delegate calls:

invoke?.resolve(jsObject)
destroy()

destroy() runs synchronously right after resolve() on the same main queue and does:

dismantleCamera()       // stops AVCaptureSession (can block main thread)
invoke = nil
if windowed {
    webView.isOpaque = true                    // restores webview
    webView.backgroundColor = backgroundColor
    webView.scrollView.backgroundColor = backgroundColor
}

In non-windowed mode, the if windowed block is skipped and resolve() works fine. In windowed mode, the webview property restoration (isOpaque, backgroundColor) appears to interfere with the pending IPC callback delivery before it reaches the JavaScript layer.

Potential fix

Reorder the operations in metadataOutput so that resolve() is called after all cleanup and webview restoration, not before:

self.isScanning = false
dismantleCamera()
if windowed {
    let backgroundColor = previousBackgroundColor ?? UIColor.white
    webView.isOpaque = true
    webView.backgroundColor = backgroundColor
    webView.scrollView.backgroundColor = backgroundColor
}
invoke?.resolve(jsObject)
invoke = nil

This way the webview is fully restored to its normal (opaque) state before the IPC response is sent — matching the state where resolve() is known to work (non-windowed mode).

I've tested this fix locally and it resolves the issue. Happy to submit a PR if this approach looks correct.

Expected behavior

scan({ windowed: true }) should resolve with the scanned barcode content, same as windowed: false.

Platform and versions

  • iOS 18.7 (iPhone)
  • Tauri v2
  • @tauri-apps/plugin-barcode-scanner v2.4.4
  • tauri-plugin-barcode-scanner from v2 branch

Stack trace

No crash — the promise simply never settles.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions