Contents

mercari/qrscanner

A modern QR Code scanner framework for iOS with comprehensive SwiftUI and UIKit support. Delivers a native iOS scanning experience with advanced customization options. Written in Swift.

Features

  • 🎯 Native iOS Design - Matches iPhone's built-in camera scanning experience
  • πŸš€ SwiftUI & UIKit Support - Full compatibility with both modern and traditional iOS development
  • πŸ“± iOS 14.0+ - Built for modern iOS with latest Swift features
  • ⚑ Easy Integration - Simple setup with comprehensive examples
  • 🎨 Highly Customizable - Extensive configuration options for focus frame, animations, and effects
  • πŸ’‘ Production Ready - Battle-tested in Mercari's production apps

Quick Start: SwiftUI Example | UIKit Example

Requirements

  • iOS: 14.0+
  • Swift: 5.9+
  • Xcode: 16.0+

Installation

Swift Package Manager (Recommended)

Via Xcode (Easiest)
  1. File β†’ Add Package Dependencies
  2. Enter: https://github.com/mercari/QRScanner.git
  3. Select version and add to your target
Via Package.swift
dependencies: [
    .package(url: "https://github.com/mercari/QRScanner.git", .upToNextMajor(from: "2.0.1"))
]
Import
import QRScanner

Quick Setup

1. Camera Permission

Add camera usage description to your Info.plist:

<key>NSCameraUsageDescription</key>
<string>Camera access is required for QR code scanning</string>

<img src="https://raw.githubusercontent.com/mercari/QRScanner/master/images/privacy_camera.png" width="500">

2. Choose Your Framework

  • SwiftUI: Modern declarative UI (recommended for new projects)
  • UIKit: Traditional imperative UI (for existing projects)

Complete examples: SwiftUI Sample | UIKit Sample

SwiftUI Usage

πŸ’‘ Recommended for new projects - SwiftUI provides a more modern and concise API

Basic Implementation

import SwiftUI
import QRScanner
import AVFoundation

struct ContentView: View {
    @State private var scannedCode = ""
    @State private var isPresented = false
    
    var body: some View {
        VStack {
            Button("Start Scanning") {
                isPresented = true
            }
            
            if !scannedCode.isEmpty {
                Text("Scanned: \(scannedCode)")
            }
        }
        .sheet(isPresented: $isPresented) {
            QRScannerSwiftUIView(
                onSuccess: { code in
                    scannedCode = code
                    isPresented = false
                },
                onFailure: { error in
                    print("Scan error: \(error)")
                    isPresented = false
                }
            )
        }
    }
}

Advanced Implementation

struct AdvancedQRScannerView: View {
    @State private var scannedCode = ""
    @State private var isScanning = true
    @State private var torchActive = false
    
    var body: some View {
        VStack {
            QRScannerSwiftUIView(
                configuration: .init(
                    focusImagePadding: 12.0,
                    animationDuration: 0.3,
                    isBlurEffectEnabled: true
                ),
                isScanning: $isScanning,
                torchActive: $torchActive,
                onSuccess: { code in
                    scannedCode = code
                    isScanning = false
                },
                onFailure: { error in
                    print("Error: \(error.localizedDescription)")
                }
            )
            
            HStack {
                Button(isScanning ? "Pause" : "Resume") {
                    isScanning.toggle()
                }
                
                Button("Torch") {
                    torchActive.toggle()
                }
            }
            .padding()
        }
    }
}

Configuration Options

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | focusImage | UIImage? | nil | Custom focus frame image | | focusImagePadding | CGFloat | 8.0 | Focus frame padding | | animationDuration | Double | 0.5 | Animation duration | | isBlurEffectEnabled | Bool | false | Enable blur effect |

| Binding | Type | Description | |---------|------|-------------| | isScanning | Bool | Control scanning state | | torchActive | Bool | Control torch state |

| Callback | Description | |----------|-------------| | onSuccess | Called when QR code is successfully scanned | | onFailure | Called when scanning fails | | onTorchActiveChange | Called when torch state changes |

πŸ“˜ Complete Example: QRScannerSwiftUISample

UIKit Usage

πŸ”§ For existing projects - UIKit integration with full customization support

Basic Implementation

import QRScanner
import AVFoundation

final class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        setupQRScanner()
    }

    private func setupQRScanner() {
        switch AVCaptureDevice.authorizationStatus(for: .video) {
        case .authorized:
            setupQRScannerView()
        case .notDetermined:
            AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in
                if granted {
                    DispatchQueue.main.async { [weak self] in
                        self?.setupQRScannerView()
                    }
                }
            }
        default:
            showAlert()
        }
    }

    private func setupQRScannerView() {
        let qrScannerView = QRScannerView(frame: view.bounds)
        view.addSubview(qrScannerView)
        qrScannerView.configure(delegate: self, input: .init(isBlurEffectEnabled: true))
        qrScannerView.startRunning()
    }

    private func showAlert() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
            let alert = UIAlertController(title: "Error", message: "Camera is required to use in this application", preferredStyle: .alert)
            alert.addAction(.init(title: "OK", style: .default))
            self?.present(alert, animated: true)
        }
    }
}

extension ViewController: QRScannerViewDelegate {
    func qrScannerView(_ qrScannerView: QRScannerView, didFailure error: QRScannerError) {
        print(error)
    }

    func qrScannerView(_ qrScannerView: QRScannerView, didSuccess code: String) {
        print(code)
    }
}

Customization

override func viewDidLoad() {
        super.viewDidLoad()

        let qrScannerView = QRScannerView(frame: view.bounds)

        // Customize focusImage, focusImagePadding, animationDuration
        qrScannerView.focusImage = UIImage(named: "scan_qr_focus")
        qrScannerView.focusImagePadding = 8.0
        qrScannerView.animationDuration = 0.5

        qrScannerView.configure(delegate: self)
        view.addSubview(qrScannerView)
        qrScannerView.startRunning()
}
Interface Builder Way

|Setup Custom Class|Customize| |-|-| |<img src="https://raw.githubusercontent.com/mercari/QRScanner/master/images/ib2.png" width="350">|<img src="https://raw.githubusercontent.com/mercari/QRScanner/master/images/ib1.png" width="350">|

Adding Flash Control

Example: FlashButton.swift

final class ViewController: UIViewController {

    ...

    @IBOutlet var flashButton: FlashButton!

    @IBAction func tapFlashButton(_ sender: UIButton) {
        qrScannerView.setTorchActive(isOn: !sender.isSelected)
    }
}

extension ViewController: QRScannerViewDelegate {

    ...

    func qrScannerView(_ qrScannerView: QRScannerView, didChangeTorchActive isOn: Bool) {
        flashButton.isSelected = isOn
    }
}

Adding Blur Effect

qrScannerView.configure(delegate: self, input: .init(isBlurEffectEnabled: true))

πŸ“˜ Complete Example: QRScannerSample

Committers

Contribution

Please read the CLA carefully before submitting your contribution to Mercari. Under any circumstances, by submitting your contribution, you are deemed to accept and agree to be bound by the terms and conditions of the CLA.

https://www.mercari.com/cla/

License

Copyright 2019 Mercari, Inc.

Licensed under the MIT License.

Package Metadata

Repository: mercari/qrscanner

Default branch: master

README: README.md