Um einen Ionic Barcode-Scanner mit Capacitor zu bauen, erstelle eine Ionic App mit der Capacitor Integration, installiere das offizielle @capacitor/barcode-scanner Plugin, erhöhe die Android-API-Version minSdkVersion auf 26, füge die iOS-Kamera-Beschreibung hinzu und rufe die Funktion CapacitorBarcodeScanner.scanBarcode() über einen Button auf. Das Plugin öffnet einen nativen Full-Screen-Scanner auf iOS und Android, einen Browser-Scanner im Web und gibt den dekodierten Text sowie das Format zurück.
In diesem Tutorial bauen wir eine funktionierende Scanner-App mit Ionic Angular auf Capacitor 8. Danach behandeln wir Format-Filterung, Error Handling, Android Scanning Engines, Continuous Scanning, Enterprise-Hardware-Scanner und häufige Fehler.
Das wirst du bauen
Eine Single-Page Ionic App mit einem Scan-Button, der:
- den Kamera-Scanner öffnet,
- jedes Ergebnis inklusive Format anzeigt,
- lets you copy or open the scanned value,
- behandelt Abbruch- und Berechtigungsfehler sauber.
Die code verwendet Angular-Standalone-Komponenten. Die Plugin-Aufrufe sind in React und Vue identisch, es ändert sich nur die Template-Syntax.
Voraussetzungen
- Node.js LTS und Bun.
- Für Android: Android Studio mit einem SDK sowie ein Gerät oder Emulator mit Kamera.
- Für iOS: ein Mac mit Xcode 26, den App Store Connect für Uploads seit April 2026 verlangt.
- Ein physisches Gerät ist am besten. Emulatoren können mit einer virtuellen Kamera scannen, aber für Barcodes sind echtes Licht und Fokus entscheidend.
Schritt 1: Ionic-App erstellen
bunx @ionic/cli start barcode-scanner blank --type=angular-standalone --capacitor
cd barcode-scanner
Das Ionic CLI erstellt die Angular-App mit @capacitor/core und @capacitor/cli bereits installiert. Prüfen Sie capacitor.config.ts: appId sollte Ihre Reverse-Domain-ID sein, zum Beispiel com.example.scanner, und webDir sollte mit dem Build-Output übereinstimmen (www für Ionic Angular).
Einmal bauen, damit die nativen Plattformen etwas zum Kopieren haben:
bun run build
Schritt 2: Android und iOS hinzufügen
bun add @capacitor/android @capacitor/ios
bunx cap add android
bunx cap add ios
Capacitor 8 erstellt neue iOS-Projekte mit Swift Package Manager. Wenn Sie stattdessen eine ältere App aktualisieren, folgen Sie der Capacitor 8 Upgrade-Guide zuerst.
Schritt 3: Barcode-Scanner-Plugin installieren
bun add @capacitor/barcode-scanner
bunx cap sync
@capacitor/barcode-scanner ist der offizielle Scanner des Capacitor-Teams. Version 3.x unterstützt Capacitor 8.
Android-Konfiguration
Das Plugin erfordert Android API 26 oder höher. Capacitor 8 ist standardmäßig auf 24 gesetzt, daher muss android/variables.gradle:
ext {
minSdkVersion = 26
// keep the other values Capacitor generated
}
Auf Android kannst du den Decoding-Engine pro Scan wählen: ZXing (Standard, unterstützt jedes Format, das das Plugin auflistet) oder Google ML Kit (schnell bei schwierigen Codes, aber ohne MaxiCode, RSS-14, RSS Expanded oder UPC/EAN-Erweiterung).
iOS-Konfiguration
Füge eine Beschreibung zur Kameranutzung hinzu in ios/App/App/Info.plist:
<key>NSCameraUsageDescription</key>
<string>The camera is used to scan barcodes and QR codes.</string>
Ohne diese wird die App von iOS beim ersten Kamerazugriff beendet. Formuliere den Satz für den Nutzer, da ihn auch die App-Review liest. iOS nutzt Apples Vision-Framework, das jedes aufgeführte Format unterstützt, außer MaxiCode und UPC/EAN-Erweiterung, und unterscheidet nicht zwischen UPC-A und EAN-13.
Schritt 4: Scanner-Service schreiben
Plugin-Aufrufe in einem Service zu halten, macht sie leicht wiederverwendbar und testbar.
// src/app/services/scanner.service.ts
import { Injectable } from '@angular/core';
import { Capacitor } from '@capacitor/core';
import {
CapacitorBarcodeScanner,
CapacitorBarcodeScannerAndroidScanningLibrary,
CapacitorBarcodeScannerCameraDirection,
CapacitorBarcodeScannerScanOrientation,
CapacitorBarcodeScannerTypeHint,
CapacitorBarcodeScannerTypeHintALLOption,
} from '@capacitor/barcode-scanner';
export interface ScanResult {
value: string;
format: string;
scannedAt: Date;
}
export type ScanOutcome =
| { status: 'ok'; result: ScanResult }
| { status: 'cancelled' }
| { status: 'denied' }
| { status: 'error'; message: string };
@Injectable({ providedIn: 'root' })
export class ScannerService {
async scan(onlyQr = false): Promise<ScanOutcome> {
try {
const { ScanResult, format } = await CapacitorBarcodeScanner.scanBarcode({
hint: onlyQr
? CapacitorBarcodeScannerTypeHint.QR_CODE
: CapacitorBarcodeScannerTypeHintALLOption.ALL,
scanInstructions: 'Point the camera at a barcode',
cameraDirection: CapacitorBarcodeScannerCameraDirection.BACK,
scanOrientation: CapacitorBarcodeScannerScanOrientation.ADAPTIVE,
cancelButtonAccessibilityLabel: 'Cancel scanning',
torchButtonOnAccessibilityLabel: 'Turn flashlight off',
torchButtonOffAccessibilityLabel: 'Turn flashlight on',
android: {
scanningLibrary: CapacitorBarcodeScannerAndroidScanningLibrary.MLKIT,
},
web: {
showCameraSelection: true,
},
});
return {
status: 'ok',
result: {
value: ScanResult,
format: CapacitorBarcodeScannerTypeHint[format] ?? String(format),
scannedAt: new Date(),
},
};
} catch (err: any) {
const code: string = err?.code ?? '';
if (code.endsWith('0006')) return { status: 'cancelled' };
if (code.endsWith('0007')) return { status: 'denied' };
return { status: 'error', message: err?.message ?? 'Scan failed' };
}
}
get platform() {
return Capacitor.getPlatform();
}
}
Das Plugin gibt Fehler mit Codes in der Form zurück OS-PLUG-BARC-XXXX. Auf iOS 0006 bedeutet, dass der Nutzer abgebrochen hat, und 0007 bedeutet, dass der Kamerazugriff verweigert wurde. Behandle Abbruch als normales Ergebnis, nicht als Fehler. Protokolliere den rohen code auf Android während des Testens, um die gleiche Zuordnung auf deinen Zielgeräten zu bestätigen.
Der format im Ergebnis steht ein numerischer Enum-Wert. CapacitorBarcodeScannerTypeHint[format] wandelt ihn zurück in einen Namen wie QR_CODE oder EAN_13.
Schritt 5: Seite erstellen
// src/app/home/home.page.ts
import { Component, inject, signal } from '@angular/core';
import { DatePipe } from '@angular/common';
import {
IonHeader, IonToolbar, IonTitle, IonContent, IonList, IonItem, IonLabel,
IonFab, IonFabButton, IonIcon, IonToggle, IonNote, ToastController,
} from '@ionic/angular/standalone';
import { addIcons } from 'ionicons';
import { scanOutline } from 'ionicons/icons';
import { ScannerService, ScanResult } from '../services/scanner.service';
@Component({
selector: 'app-home',
templateUrl: 'home.page.html',
imports: [
DatePipe, IonHeader, IonToolbar, IonTitle, IonContent, IonList, IonItem,
IonLabel, IonFab, IonFabButton, IonIcon, IonToggle, IonNote,
],
})
export class HomePage {
private scanner = inject(ScannerService);
private toast = inject(ToastController);
results = signal<ScanResult[]>([]);
onlyQr = signal(false);
scanning = signal(false);
constructor() {
addIcons({ scanOutline });
}
async scan() {
if (this.scanning()) return;
this.scanning.set(true);
const outcome = await this.scanner.scan(this.onlyQr());
this.scanning.set(false);
if (outcome.status === 'ok') {
this.results.update((list) => [outcome.result, ...list]);
} else if (outcome.status === 'denied') {
await this.show('Camera access is off. Enable it in Settings to scan.');
} else if (outcome.status === 'error') {
await this.show(outcome.message);
}
}
private async show(message: string) {
const t = await this.toast.create({ message, duration: 3000 });
await t.present();
}
}
<!-- src/app/home/home.page.html -->
<ion-header>
<ion-toolbar>
<ion-title>Barcode Scanner</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<ion-item>
<ion-toggle [checked]="onlyQr()" (ionChange)="onlyQr.set($event.detail.checked)">
QR codes only
</ion-toggle>
</ion-item>
@for (r of results(); track r.scannedAt) {
<ion-item>
<ion-label>
<h2>{{ r.value }}</h2>
<p>{{ r.format }} · {{ r.scannedAt | date: 'shortTime' }}</p>
</ion-label>
</ion-item>
} @empty {
<ion-item lines="none">
<ion-note>No scans yet. Tap the button to scan.</ion-note>
</ion-item>
}
</ion-list>
<ion-fab slot="fixed" vertical="bottom" horizontal="end">
<ion-fab-button (click)="scan()" [disabled]="scanning()" aria-label="Scan a barcode">
<ion-icon name="scan-outline" aria-hidden="true"></ion-icon>
</ion-fab-button>
</ion-fab>
</ion-content>
Schritt 6: Auf Geräten ausführen
bun run build
bunx cap sync
bunx cap run android
bunx cap run ios
Für die Web-Version bun run start wird die App auf localhost bereitgestellt, wo Browser Kamerazugriff erlauben. Bei einer LAN-IP brauchst du HTTPS.
Für schnellere Iterationen nutze Live Reload auf einem Gerät: bunx cap run android --livereload --external lädt die App bei jedem Speichern neu.
Unterstützte Formate
| Format | Android ZXing | Android ML Kit | iOS |
|---|---|---|---|
| QR Code, Aztec, Data Matrix, PDF417 | Ja | Ja | Ja |
| EAN-13, EAN-8, UPC-A, UPC-E | Ja | Ja | Ja (UPC-A und EAN-13 gleich behandelt) |
| Code 128, Code 39, Code 93, Codabar, ITF | Ja | Ja | Ja |
| RSS-14, RSS Expanded | Ja | Nein | Ja |
| MaxiCode | Ja | Nein | Nein |
| UPC/EAN-Erweiterung | Ja | Nein | Nein |
Wenn Sie ein nicht unterstütztes Format als hintübergeben, scannt das Plugin als Fallback alle Formate.
Sicherer Umgang mit Scan-Werten
Ein Barcode ist unzuverlässige Eingabe. Bevor du ihn verarbeitest:
- Validiere URLs, bevor du sie öffnest, und zeige dem Nutzer zuerst die Domain an.
- Führe gescannte Strings nicht als code aus und injiziere sie nicht in HTML.
- Validiere bei Produktcodes die Prüfziffer, bevor du deinen Backend aufrufst.
- Rate-limite Backend-Abfragen, falls Nutzer schnell scannen können.
Mehr als nur ein einfacher Scanner
scanBarcode ist ein einmaliger, vollbildiger Flow. Das deckt die meisten Apps ab. Andere Anforderungen erfordern andere Tools:
- Kontinuierliches Scannen in Ihrer eigenen UI. Führen Sie eine Kamera-Feed hinter Ihrer Web-UI mit @capgo/camera-previewaus, greifen Sie Frames mit
captureSampleab und dekodieren Sie sie in JavaScript mit einer Bibliothek wiezxing-wasm. - Robuste Enterprise-Geräte. Zebra-Handhelds haben Hardware-Scanner. @capgo/capacitor-zebra-datawedge verwaltet DataWedge-Profile und Scan-Trigger, was auf diesen Geräten schneller und zuverlässiger ist als die Kamera.
- Dokumentenerfassung. Für Belege und Papierformulare eignet sich ein Dokumentenscanner-Plugin besser als ein Barcode-Reader. Siehe das Plugin-Verzeichnis.
Kommst du von Cordova? Unser Cordova-Barcode-Scanner-Guide deckt die alten Plugins und einen Migrationspfad ab.
Fehlerbehebung
iOS-App schließt sich beim Scan-Start. NSCameraUsageDescription is missing from Info.plist.
Android-Build schlägt mit einem Manifest-Merger- oder minSdk-Fehler fehl. minSdkVersion ist noch 24. Setze es auf 26 in android/variables.gradle and sync.
Scanner öffnet sich, liest aber den code nie. Versuche die andere Android-Engine, verbessere die Beleuchtung, nutze die Taschenlampe und schränke hint auf das erwartete Format ein. Sehr kleine oder beschädigte Codes erfordern einen geringeren Abstand.
Berechtigung wird jedes Mal verweigert. Der Nutzer hat den Kamerazugriff zuvor abgelehnt. Zeige eine Nachricht an, die erklärt, wie man ihn in den Systemeinstellungen aktiviert. Das OS zeigt den Prompt nicht erneut an.
Web-Scanner startet nicht. Die Seite wird nicht über HTTPS oder localhost bereitgestellt, oder ein anderer Tab nutzt die Kamera.
Ausrichtungseinstellung wird auf Tablets ignoriert. Ab Apps, die auf Android SDK 36 abzielen, ignoriert Android 16 feste Ausrichtungen auf großen Bildschirmen. Nutze ADAPTIVE.
Fixes deployen, ohne auf das Review zu warten
Sobald der Scanner live ist, sind die meisten Änderungen – etwa neue Validierungsregeln, UI-Tweaks oder ein anderer Backend-Lookup – Web code. Capgo Live Updates pushe sie innerhalb von Minuten an installierte Apps, während Plugin-Upgrades und Info.plist Änderungen weiterhin über die Stores laufen. Wenn du einen neuen Native Build ohne lokales Mac-Setup brauchst, Capgo Build baut signierte iOS- und Android-Binaries in der Cloud.
Fazit
Ein Ionic Barcode-Scanner auf Capacitor 8 braucht ein offizielles Plugin, zwei native Config-Änderungen (Android minSdkVersion 26 und den iOS-Kamera-Nutzungstext) und einen einzigen scanBarcode Call. Schränke Formate ein, wo möglich, behandle Abbruch- und Permission-Fehler als normale Ergebnisse, validiere alles, was du scannst, und nutze Camera Preview oder DataWedge, wenn der One-Shot-Flow nicht ausreicht.