メインコンテンツへスキップ

Capacitor を使った Ionic バーコードスキャナーの構築方法

Capacitor 8 で Ionic バーコードスキャナーを構築:アプリのセットアップ、@capacitor/barcode-scanner の追加、iOS、Android、Web での QR コードとバーコードのスキャン。

ハイブリッドモバイルアプリ内でバーコードをスキャンするスマートフォンのイラスト

CapacitorでIonicバーコードスキャナーを構築するには、Capacitor統合を含むIonicアプリを作成し、公式の @capacitor/barcode-scanner プラグインをインストールし、Androidの minSdkVersion を26に引き上げ、iOSのカメラ使用説明を追加し、 CapacitorBarcodeScanner.scanBarcode() をボタンから呼び出します。このプラグインはiOSとAndroidでネイティブのフルスクリーンスキャナーを、Webではブラウザのスキャナーを開き、デコードされたテキストと形式を返します。

本チュートリアルでは、Capacitor 8 上で Ionic Angular を用いて動作するスキャナーアプリを構築し、フォーマットフィルタリング、エラー処理、Android のスキャンエンジン、連続スキャン、エンタープライズ向けハードウェアスキャナー、および一般的な失敗事例について解説します。

構築する内容

スキャンボタンを備えた単一ページ Ionic アプリで、以下を行います:

  • カメラスキャナーを開く
  • 各結果とそのフォーマットを表示する
  • スキャンした値をコピーまたは開く
  • キャンセルや権限エラーを適切に処理する

code は Angular のスタンドアロンコンポーネントを使用しています。プラグインの呼び出しは React や Vue でも同一なので、テンプレート構文のみが異なります。

前提条件

  • Node.js LTS と Bun。
  • Android の場合:SDK を備えた Android Studio、およびカメラ付きのデバイスまたはエミュレーター。
  • iOS の場合:2026 年 4 月以降、App Store Connect でのアップロードに必須となった Xcode 26 を搭載した Mac。
  • 実機が最適です。エミュレータは仮想カメラでスキャンできますが、バーコードでは実際の照明やピントが重要になります。

ステップ 1: Ionic アプリの作成

bunx @ionic/cli start barcode-scanner blank --type=angular-standalone --capacitor
cd barcode-scanner

Ionic の CLI は、Angular アプリを以下でスキャフォールドします @capacitor/core と @capacitor/cli がインストール済みです。確認してください capacitor.config.ts: appId はリバーシドドメインIDにしてください。例: com.example.scannerや webDir はビルド出力と一致させる必要があります (www Ionic Angular の場合)。

ネイティブプラットフォームにコピーするものがあるように、一度ビルドしてください:

bun run build

ステップ 2: Android と iOS の追加

bun add @capacitor/android @capacitor/ios
bunx cap add android
bunx cap add ios

Capacitor 8 では Swift Package Manager を使って新しい iOS プロジェクトを作成します。既存のアプリをアップグレードする場合は、以下の Capacitor 8 アップグレードガイド してください。

ステップ 3: バーコードスキャナープラグインのインストール

bun add @capacitor/barcode-scanner
bunx cap sync

@capacitor/barcode-scanner に従ってください。これは Capacitor チーム公式のスキャナーです。バージョン 3.x は Capacitor 8 に対応しています。

Android の設定

このプラグインには Android API 26 以上が必要です。Capacitor 8 のデフォルトは 24 なので、次を編集してください。 android/variables.gradle:

ext {
    minSdkVersion = 26
    // keep the other values Capacitor generated
}

Android ではスキャンごとにデコードエンジンを選べます。ZXing(デフォルト、プラグインが列挙するすべてのフォーマットに対応)または Google ML Kit(難しいコードでも高速だが、MaxiCode、RSS-14、RSS Expanded、UPC/EAN extension には非対応)です。

iOSの設定

カメラの使用目的(usage description)を次に追加してください: ios/App/App/Info.plist:

<key>NSCameraUsageDescription</key>
<string>The camera is used to scan barcodes and QR codes.</string>

これがないと、iOS は最初のカメラアクセス時にアプリを強制終了します。App Review も読むので、ユーザーに向けた文章を書きましょう。iOS は Apple の Vision framework を使用しており、MaxiCode と UPC/EAN extension を除くリスト内のすべてのフォーマットに対応していますが、UPC-A と EAN-13 は区別しません。

ステップ 4: スキャナーサービスの実装

プラグイン呼び出しをサービスにまとめることで、再利用とテストが容易になります。

// 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();
  }
}

このプラグインは、以下の形式のコード付きエラーを返します OS-PLUG-BARC-XXXX。iOSでは、 0006 はユーザーがキャンセルしたことを示し、 0007 はカメラへのアクセスが拒否されたことを示します。キャンセルはエラーではなく正常な結果として扱ってください。テスト中はAndroidで生のcodeをログに出力し、対象デバイスでも同じマッピングが適用されることを確認してください。

この format 結果内のは数値の列挙型(enum)値です。 CapacitorBarcodeScannerTypeHint[format] はこれを「」などの名前に変換します QR_CODE または EAN_13.

ステップ 5: ページのビルド

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

ステップ 6: 実機で実行

bun run build
bunx cap sync
bunx cap run android
bunx cap run ios

Web版の場合、 bun run start ブラウザがカメラへのアクセスを許可する localhost でアプリを配信します。LAN IP 経由では HTTPS が必要です。

より速く反復するには、デバイスで live reload を使ってください: bunx cap run android --livereload --external 保存するたびにアプリがリロードされます。

対応フォーマット

フォーマット Android版 ZXing Android ML Kit iOSと
QR Code、Aztec、Data Matrix、PDF417 はい はい はい
EAN-13、EAN-8、UPC-A、UPC-E はい はい はい(UPC-A と EAN-13 は同様に扱われます)
Code 128、Code 39、Code 93、Codabar、ITF はい はい はい
RSS-14、RSS Expanded はい いいえ はい
MaxiCode はい いいえ いいえ
UPC/EAN 拡張 はい いいえ いいえ

サポートされていないフォーマットを hintとして渡すと、プラグインは任意のフォーマットのスキャンにフォールバックします。

スキャン値の安全な取り扱い

バーコードは信頼できない入力です。処理する前に:

  • URL を開く前に検証し、まずユーザーにドメインを表示してください。
  • スキャンされた文字列を code として実行したり、HTML に注入したりしないでください。
  • 商品コードの場合、バックエンドを呼び出す前にチェックディジットを検証してください。
  • ユーザーが素早くスキャンできる場合、バックエンドのルックアップにレートリミットを適用してください。

基本的なスキャナーを超えて

scanBarcode はワンショットのフルスクリーンフローです。ほとんどのアプリにはこれで十分です。その他のニーズには別のツールが必要です:

  • 独自の UI での連続スキャン。 Web UI の背後でカメラフィードを @capgo/camera-previewで実行し、 captureSampleでフレームを取得し、JavaScript でライブラリ(例:)を使ってデコードします。 zxing-wasm.
  • 堅牢なエンタープライズ端末。 Zebra製ハンディ端末にはハードウェアスキャナーが搭載されています。 @capgo/capacitor-zebra-datawedge DataWedgeプロファイルとスキャントリガーを管理でき、これらの端末のカメラを使うより高速で信頼性も高いです。
  • ドキュメントキャプチャ。 レシートや紙の帳票には、バーコードリーダーよりドキュメントスキャナープラグインが適しています。詳細は プラグインディレクトリ.

Cordovaからの移行ですか?当社の Cordovaバーコードスキャナーガイド では、旧プラグインと移行パスについて解説しています。

トラブルシューティング

スキャンを開始するとiOSアプリが終了する。 NSCameraUsageDescription が見つかりません Info.plist.

マニフェストのマージエラーや minSdk エラーにより、Android のビルドが失敗します。 minSdkVersion は依然として24のままです。 android/variables.gradle で設定し、同期してください。

スキャナーは開くが、codeを読み取らない。 別のAndroidエンジンを使用し、照明を改善、フラッシュを使い、 hint 想定するフォーマットに制限してください。非常に小さい、または損傷したコードはカメラを近づける必要があります。

毎回権限が拒否される。 ユーザーが以前にカメラへのアクセスを拒否しました。システム設定で有効にする方法を説明するメッセージを表示してください。OSはプロンプトを再度表示しません。

Webスキャナーが起動しない。 ページがHTTPSまたはlocalhost経由で配信されていないか、別のタブがカメラを占有しています。

タブレットで向き設定が反映されない。 Android SDK 36 をターゲットとするアプリでは、Android 16 は大型スクリーンで固定方向を無視します。次を使用してください ADAPTIVE.

レビューを待たずに修正をリリース

スキャナーが稼働すれば、新しいバリデーションルールやUIの微調整、バックエンドの参照先の変更といったほとんどの変更は、Web code で完結します。 Capgo Live Updates 数分以内にインストール済みのアプリへ反映できます。一方で、プラグインのアップグレードや Info.plist 変更は引き続きストア経由です。ローカルにMac環境がない状態で新しい Native Build が必要な場合は、 Capgo のビルド クラウド上で署名済みのiOSおよびAndroidバイナリを生成します。

まとめ

Capacitor 8 での Ionic バーコードスキャナーには、公式プラグイン1つ、ネイティブ設定変更2つ(Android minSdkVersion 26 と iOS のカメラ使用許可文字列)、そして1回の scanBarcode 呼び出しが必要です。可能な限りフォーマットを制限し、キャンセルや権限エラーは通常の結果として扱い、スキャンした内容はすべてバリデーションしてください。単発のスキャンフローで足りない場合は、カメラプレビューや DataWedge の利用を検討しましょう。

Capacitor アプリ向けの Live Updates

Web レイヤーのバグが本番で発生したら、App Store の審査を何日も待たずに Capgo で修正を配信しましょう。ネイティブの変更は通常の審査フローに残したまま、ユーザーはバックグラウンドでアップデートを受け取れます。

最新のブログ記事

Capgoは、真にプロフェッショナルなモバイルアプリを作るために必要な最高のインサイトを提供します。