tauri-apps / tauri-apps/plugins-workspace

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

Open
#3,348 0 comments 0 reactions 0 assignees View on GitHub
platform: ios plugin: barcode-scanner type: bug
Dominant language
Rust
Stars
1.8k
Forks
602
Avg merge
4d 14h
Merged PRs (30d)
9

Description

### 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

```typescript
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:

```swift
invoke?.resolve(jsObject)
destroy()
```

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

```swift
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:

```swift
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._

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.