Zum Hauptinhalt springen

So baust du einen Ionic Barcode-Scanner mit Capacitor

Baue einen Ionic Barcode-Scanner mit Capacitor 8: App einrichten, @capacitor/barcode-scanner hinzufügen, QR-Codes und Barcodes auf iOS, Android und Web scannen.

Illustration eines Smartphones, das einen Barcode in einer Hybrid-App scannt

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 wie zxing-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.

Live-Updates für Capacitor-Apps

Wenn ein Bug im Web-Layer live ist, liefere den Fix über Capgo aus, statt Tage auf die App-Store-Freigabe zu warten. Nutzer bekommen das Update im Hintergrund, während native Änderungen im normalen Review-Prozess bleiben.

Neues aus unserem Blog

Capgo gibt dir die besten Insights, um eine wirklich professionelle Mobile App zu erstellen.