본문 바로가기

Capacitor를 사용한 Ionic 바코드 스캐너 빌드 방법

Capacitor 8로 Ionic 바코드 스캐너 만들기: 앱 설정, @capacitor/barcode-scanner 추가, iOS·Android·웹에서 QR 코드 및 바코드 스캔

하이브리드 모바일 앱에서 바코드를 스캔하는 스마트폰 일러스트

Capacitor로 Ionic 바코드 스캐너를 만들려면 Capacitor 통합이 포함된 Ionic 앱을 생성하고 공식 @capacitor/barcode-scanner 플러그인을 설치하고 Android의 minSdkVersion 을 26으로 설정하고 iOS 카메라 사용 설명을 추가한 뒤 CapacitorBarcodeScanner.scanBarcode() 을 버튼에서 호출하세요. 이 플러그인은 iOS와 Android에서 네이티브 풀스크린 스캐너를, 웹에서는 브라우저 스캐너를 열고 디코딩된 텍스트와 포맷을 반환합니다.

이 튜토리얼은 Capacitor 8 기반 Ionic Angular로 동작하는 스캐너 앱을 구축한 후, 형식 필터링, 에러 처리, Android 스캐닝 엔진, 연속 스캐닝, 엔터프라이즈 하드웨어 스캐너, 그리고 일반적인 실패 사례를 다룹니다.

구현할 내용

스캔 버튼이 있는 단일 페이지 Ionic 앱으로 다음을 수행합니다:

  • 카메라 스캐너를 엽니다.
  • 각 결과와 해당 형식을 표시합니다.
  • 스캔된 값을 복사하거나 열 수 있습니다.
  • 취소 및 권한 오류를 깔끔하게 처리합니다.

code는 Angular 스탠드얼론 컴포넌트를 사용합니다. 플러그인 호출은 React나 Vue에서도 동일하므로 템플릿 문법만 다릅니다.

사전 요구사항

  • Node.js LTS와 Bun.
  • Android의 경우: SDK가 포함된 Android Studio와 카메라가 있는 디바이스 또는 에뮬레이터.
  • iOS의 경우: 2026년 4월부터 업로드에 필수인 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 설정

다음에 카메라 사용 설명을 추가하세요 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 프레임워크를 사용하며, 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

웹 버전의 경우, bun run start 앱을 localhost에서 서빙하며, 브라우저는 카메라 접근을 허용합니다. LAN IP를 통해 접근하려면 HTTPS가 필요합니다.

더 빠르게 반복 개발하려면, 디바이스에서 라이브 리로드를 사용하세요: 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에서 연속 스캔. 웹 UI 뒤에서 카메라 피드를 실행하려면 다음을 사용하세요: @capgo/camera-preview프레임을 캡처하려면 다음을 사용하세요: captureSampleJavaScript에서는 다음과 같은 라이브러리로 디코딩하세요: zxing-wasm.
  • 견고한 엔터프라이즈 기기. Zebra 핸드헬드 기기에는 하드웨어 스캐너가 있습니다. @capgo/capacitor-zebra-datawedge DataWedge 프로필과 스캔 트리거를 관리하며, 해당 기기의 카메라보다 빠르고 안정적입니다.
  • 문서 캡처. 영수증이나 종이 양식에는 바코드 리더보다 문서 스캐너 플러그인이 더 적합합니다. 다음을 참고하세요: 플러그인 디렉토리.

Cordova에서 넘어오셨나요? 저희 Cordova 바코드 스캐너 가이드 에서 기존 플러그인과 마이그레이션 경로를 다룹니다.

문제 해결

스캔을 시작하면 iOS 앱이 종료됩니다. NSCameraUsageDescription 에서 누락되었습니다 Info.plist.

Android 빌드가 매니페스트 병합 또는 minSdk 오류로 실패합니다. minSdkVersion 여전히 24입니다. 로 26으로 설정하세요 android/variables.gradle 그리고 동기화하세요.

스캐너는 열리지만 code을 읽지 않습니다. 다른 Android 엔진을 시도하고, 조명을 개선하며, 플래시를 사용하고 hint 기대하는 형식으로 제한하세요. 매우 작거나 손상된 코드는 카메라를 더 가까이 대야 합니다.

매번 권한이 거부됩니다. 사용자가 이전에 카메라 접근을 거부했습니다. 시스템 설정에서 활성화하는 방법을 설명하는 메시지를 표시하세요. OS는 프롬프트를 다시 표시하지 않습니다.

Web 스캐너가 시작되지 않습니다. 페이지가 HTTPS 또는 localhost로 서빙되지 않거나, 다른 탭이 카메라를 점유하고 있습니다.

태블릿에서 방향 설정이 무시됩니다. Android SDK 36을 타겟으로 하는 앱부터, Android 16은 대형 화면에서 고정 방향을 무시합니다. 대신 ADAPTIVE.

심사 대기 없이 버그 수정 배포

스캐너가 라이브로 전환되면 새로운 검증 규칙, UI 조정, 또는 다른 백엔드 조회와 같은 대부분의 변경 사항은 웹 code입니다. Capgo Live Update 사용하기 몇 분 안에 설치된 앱에 이를 배포하고, 플러그인 업그레이드와 Info.plist 변경 사항은 여전히 스토어를 통해 진행됩니다. 로컬 Mac 설정 없이 새로운 네이티브 빌드가 필요할 때, Capgo 빌드 클라우드에서 서명된 iOS 및 Android 바이너리를 생성합니다.

마무리

Capacitor 8에서 Ionic 바코드 스캐너를 쓰려면 공식 플러그인 하나와 두 가지 네이티브 설정 변경(Android minSdkVersion 26 및 iOS 카메라 사용 권한 문자열), 그리고 단 한 번의 scanBarcode 호출이 필요합니다. 가능하면 형식을 제한하고, 취소 및 권한 오류는 정상적인 결과로 처리하며, 스캔한 모든 데이터를 검증하세요. 단발성 흐름으로 부족하면 카메라 프리뷰나 DataWedge를 사용하세요.

Capacitor 앱용 Live updates

웹 레이어 버그가 라이브 상태라면, 앱 스토어 승인을 며칠씩 기다리지 말고 Capgo를 통해 수정 사항을 배포하세요. 네이티브 변경 사항은 정상 리뷰 절차를 그대로 따르는 반면, 사용자는 백그라운드에서 업데이트를 받습니다.

블로그 최신 글

Capgo는 정말 프로페셔널한 모바일 앱을 만드는 데 필요한 최고의 인사이트를 제공합니다.